iOS GCD线程同步

作者: mmmickychiang7 | 来源:发表于2017-11-22 20:36 被阅读15次

    题外话
    原本打算每周至少整理两篇博客的,看来真的是高估自己了...
    在此给自己立个FLAG,一周至少一篇,恳请监督(大哭...)

    言归正传
    本篇博客主要讲解GCD的线程同步应用,对于什么是GCD的基础问题,不太明白的还是建议大家去看看基础,强烈推荐iOS多线程--彻底学会多线程之『GCD』(写的真的很棒哦)

    线程同步

    • 线程组group
    • 信号量semaphore
    • 栅栏barrier

    一. 线程组group

    可以使用dispatch_group_async函数将多个任务关联到一个线程组dispatch_group和相应的队列queue中,dispatch_group会并发地同时执行这些任务。
    而且dispatch_group可以用来阻塞一个线程,直到dispatch_group关联的所有的任务完成执行。有时候必须等待任务完成的结果,然后才能继续后面的处理。

    dispatch_group同步应用

    首先执行任务1、任务2、任务3,执行完后,接着执行任务0

    /**
    * 创建一个队列
    * 参数1:队列名(C语言写法)
    * 参数2:队列类型(串行/并行)
    * DISPATCH_QUEUE_SERIAL串行
    * DISPATCH_QUEUE_CONCURRENT 并行
    */
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
        
    // 创建一个调度组group(线程组)
    dispatch_group_t group = dispatch_group_create();
        
    // 向线程组中添加多个任务
    dispatch_group_async(group, queue, ^{
      NSLog(@"mission--1");
    });
        
    NSLog(@"===aaa===");
        
    dispatch_group_async(group, queue, ^{
      NSLog(@"mission--2");
    });
        
    dispatch_group_notify(group, queue, ^{
      NSLog(@"dispatch_group_notify:mission--0");
    });
        
    dispatch_group_async(group, queue, ^{
      NSLog(@"mission--3");
    });
    

    运行结果

    dispatch_group_01.png

    分析
    1.dispatch_group会等和它关联的所有的dispatch_queue上的任务都执行完毕才会发出同步信号,即dispathc_group_notify的代码块block才会被执行。
    2.dispathc_group_notify的作用:在group中的其他操作全部完成后,再操作自己的内容,所以我们会看到上面任务1、任务2、任务3执行之后,才执行任务0。

    问题升级

    如使用AFNetworking添加异步任务时,上述方法显然无效。
    因为网络请求需要时间,而线程的执行并不会等待请求完成后才真正算作完成,而是只负责将请求发出去,线程就认为自己的任务算完成了,当三个请求都发送出去,就会执行dispathc_group_notify中的内容,但请求结果返回的时间是不一定的,也就导致界面都刷新了,请求才返回,这就是无效的。
    因此需要使用dispatch_group_enter、dispatch_group_leave(需要成对出现)

    dispatch_group_t group = dispatch_group_create();
        
    dispatch_group_enter(group);
    [self demoBlock:^(NSString *str) {
      NSLog(@"mission--1");
      dispatch_group_leave(group);
    }];
        
    dispatch_group_enter(group);
    [self demoBlock:^(NSString *str) {
      NSLog(@"mission--2");
      dispatch_group_leave(group);
    }];
        
    dispatch_group_enter(group);
    [self demoBlock:^(NSString *str) {
      NSLog(@"mission--3");
      dispatch_group_leave(group);
    }];
        
    // 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
        
    dispatch_group_notify(group, mainQueue, ^{
      NSLog(@"mission--0");
    });
    

    为此我们还需要简单的写一个block回调
    .m文件中添加扩展,写入:

    - (void)demoBlock:(void(^)(NSString *str))block;
    

    实现方法:

    - (void)demoBlock:(void(^)(NSString *str))block {
        
        block(@"demoBlock");
    }
    

    运行结果

    dispatch_group_02.png

    分析
    1.和dispatch_async相比,当我们调用n次dispatch_group_enter后再调用n次dispatch_group_leave时,dispatch_group_notify和dispatch_group_wait会收到同步信号(这里没有给出dispatch_group_wait的具体应用,需要了解的朋友可以自行查阅相关内容)
    2.应用场景:处理异步任务的同步,当异步任务开始前调用dispatch_group_enter,异步任务结束后调用dispatch_group_leave。
    适用于后台批量下载,结束后主线程统一刷新UI。

    二. 信号量semaphore

    首先,我们必须了解什么是信号量,在这里给大家简单的介绍一下(小白可以去查一些相关资料)


    信号量.png

    以下是简单的个人理解(伪代码)

     信号量
     信号量 初始值 v = 3
     
     a come
     if v < 0 wait
     else v = v - 1
     // v = 2; state: a in
     
     b come
     if v < 0 wait
     else v = v - 1
     // v = 1; state: b in
     
     c come
     if v < 0 wait
     else v = v - 1
     // v = 0; state: c in
     
     d come
     if v < 0 wait
     else v = v - 1
     // state: d wait
     
     e come
     if v < 0 wait
     else v = v - 1
     // state: e wait
     
     f come
     if v < 0 wait
     else v = v - 1
     // state: f wait
     
     b out // v = v + 1 = 1
     if v < 0 wait
     else v = v - 1
     // v = 0; state: f in
     
     ...
    

    方法

    • dispatch_semaphore_create:创建一个信号量(semaphore)
    • dispatch_semaphore_signal:信号通知,即让信号量+1
    • dispatch_semaphore_wait:等待,直到信号量大于0时,即可操作,同时将信号量-1

    还是上面的例子

    /**
      * 获取全局队列
      * 参数1:选择的是哪个优先级的全局队列
      * 参数2:作为保留字段备用(一般为0)
    */
     dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
     // 创建线程组
     dispatch_group_t group = dispatch_group_create();
        
     // 向线程组中添加任务
    dispatch_group_async(group, globalQueue, ^{
      // 创建一个信号量 初始值为0
      dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
      [self demoBlock:^(NSString *str) {
        NSLog(@"mission--1");
        // 执行了block之后 将信号量+1
        dispatch_semaphore_signal(semaphore);
      }];
      // 在成功执行block之前,信号量必须等待
      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
        
    NSLog(@"===aaa===");
        
    dispatch_group_async(group, globalQueue, ^{
      dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
      [self demoBlock:^(NSString *str) {
        NSLog(@"mission--2");
        dispatch_semaphore_signal(semaphore);
        }];
      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
        
    dispatch_group_async(group, globalQueue, ^{
      dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
      [self demoBlock:^(NSString *str) {
        NSLog(@"mission--3");
        dispatch_semaphore_signal(semaphore);
      }];
      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
        
    dispatch_group_notify(group, globalQueue, ^{
      NSLog(@"dispatch_group_notify:mission--0");
    });
    

    运行结果

    dispatch_semaphore.png

    分析
    1.在每个请求开始之前,我们创建一个信号量,初始为0,在请求操作之后,我们设一个dispatch_semaphore_wait,在请求到结果之后,再将信号量+1,即执行dispatch_semaphore_signal。
    2.这样做的目的是保证在请求结果没有返回之前,一直让线程等待在那里,这样一个线程的任务一直在等待,就不会算作完成,notify的内容也就不会执行了,直到每个请求的结果都返回了,线程任务才能够结束,这时候notify也才能够执行。

    三. 栅栏barrier

    直接先上个例子:首先执行任务1、任务2、任务3,执行完后,接着执行任务0,任务0执行之后,再执行任务4、任务5、任务6


    barrier.png

    分别使用dispatch_barrier_sync函数和dispatch_barrier_async函数来完成

    dispatch_barrier_sync

    dispatch_queue_t queue = dispatch_queue_create("mickyQueue", DISPATCH_QUEUE_CONCURRENT);
        
    dispatch_async(queue, ^{
      NSLog(@"mission--1");
    });
        
    dispatch_async(queue, ^{
      NSLog(@"mission--2");
    });
        
    dispatch_async(queue, ^{
      NSLog(@"mission--3");
    });
        
    dispatch_barrier_sync(queue, ^{
      for (int i = 0; i < 10000; i++) {
        if (700 == i) {
          NSLog(@"point1");
        } else if (800 == i) {
          NSLog(@"point2");
        } else if (900 == i) {
          NSLog(@"point3");
        }
      }
      NSLog(@"barrier");
    });
        
    NSLog(@"===aaa===");
        
    dispatch_async(queue, ^{
      NSLog(@"mission--4");
    });
        
    NSLog(@"===bbb===");
        
    dispatch_async(queue, ^{
      NSLog(@"mission--5");
    });
        
    dispatch_async(queue, ^{
      NSLog(@"mission--6");
    });
        
    

    运行结果

    dispatch_barrier_sync.png

    dispatch_barrier_async

    dispatch_queue_t queue = dispatch_queue_create("mickyQueue", DISPATCH_QUEUE_CONCURRENT);
        
    dispatch_async(queue, ^{
      NSLog(@"mission--1");
    });
        
    dispatch_async(queue, ^{
      NSLog(@"mission--2");
    });
        
    dispatch_async(queue, ^{
      NSLog(@"mission--3");
    });
        
    dispatch_barrier_async(queue, ^{
      for (int i = 0; i < 10000; i++) {
        if (700 == i) {
          NSLog(@"point1");
        } else if (800 == i) {
          NSLog(@"point2");
        } else if (900 == i) {
          NSLog(@"point3");
        }
      }
    NSLog(@"barrier");
    });
        
    NSLog(@"===aaa===");
        
    dispatch_async(queue, ^{
      NSLog(@"mission--4");
    });
        
    NSLog(@"===bbb===");
        
    dispatch_async(queue, ^{
      NSLog(@"mission--5");
    });
        
    dispatch_async(queue, ^{
      NSLog(@"mission--6");
    });
    

    运行结果

    dispatch_barrier_async_01.png dispatch_barrier_async_02.png

    分析
    根据打印结果可以看出
    1.dispatch_barrier_sync/dispatch_barrier_async都是等待当前队列的之前插入的任务执行完之后再执行自己的任务,执行完自己的任务之后再执行当前队列的之后插入的任务。
    2.aaa、bbb的输出位置完全不同
    sync的时候,aaa、bbb的输出位置都是在任务0结束之后;
    =async的时候,aaa、bbb的输出位置可以在任务0结束之前或者任务0正在执行中,又或者任务0结束之后。
    3.barrier(dispatch_barrier_sync/dispatch_barrier_async)只是阻塞同队列中后面的操作而已,而dispatch_barrier_async不阻塞当前的线程。

    结论
    共同点
    1.等待在它前面插入队列的任务先执行完;
    2.等待他们自己的任务执行完再执行后面的任务。

    不同点
    1.dispatch_barrier_sync将自己的任务插入到队列的时候,需要等待自己的任务结束之后才会继续插入被写在它后面的任务,然后执行它们;
    2.dispatch_barrier_async将自己的任务插入到队列之后,不会等待自己的任务结束,它会继续把后面的任务插入到队列,然后等待自己的任务结束后才执行后面任务;
    3.dispatch_barrier_async不阻塞当前的线程,而dispatch_barrier_sync/dispatch_barrier_async只是阻塞同队列中后面的任务。

    最后

    若是本文内容有错误的地方,还请各位路过的大神们指点指点~

    相关文章

      网友评论

        本文标题:iOS GCD线程同步

        本文链接:https://www.haomeiwen.com/subject/lydgvxtx.html