美文网首页iOS日常iOS - 线程/底层/RuntimeiOS开发
使用dispatch_group来进行线程同步

使用dispatch_group来进行线程同步

作者: liang1991 | 来源:发表于2016-04-21 18:03 被阅读25183次

    我的上篇文章iOS中多个网络请求的同步问题总结中用到了dispatch_group来进行线程同步,对用法不是特别熟悉所以整理这篇文章来加深记忆(闲着也是闲着)

    一、简单介绍下将会用到的一些东西

    英语不好就不翻译官方文档了..

    1、dispatch_group_async

    * Submits a block to a dispatch queue and associates the block with the given
    * dispatch group
    //将一个block(代码块)加入到dispatch_queue_t queue中并和dispatch_group_t group相关联
    void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
    

    个人理解:将代码块dispatch_block_t block放入队列dispatch_queue_t queue中执行;并和调度组dispatch_group_t group相互关联;如果提交到dispatch_queue_t queue中的block全都执行完毕会调用dispatch_group_notify并且dispatch_group_wait会停止等待;

    2、dispatch_group_enter(group)、dispatch_group_leave(group)

    * Calling this function indicates another block has joined the group through
    * a means other than dispatch_group_async(). Calls to this function must be
    * balanced with dispatch_group_leave().
    调用这个方法标志着一个代码块被加入了group,和dispatch_group_async功能类似;dispatch_group_enter()、dispatch_group_leave()必须成对出现
    void dispatch_group_enter(dispatch_group_t group);
    

    个人理解:和内存管理的引用计数类似,我们可以认为group也持有一个整形变量(只是假设),当调用enter时计数加1,调用leave时计数减1,当计数为0时会调用dispatch_group_notify并且dispatch_group_wait会停止等待;

    3、dispatch_group_notify

    void dispatch_group_notify(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);
    

    个人理解:当关联到dispatch_group_t上的dispatch_group_async任务执行完毕或者是关联在上面的dispatch_group_enter、dispatch_group_leave成对出现了。参数中的dispatch_block_t block会被提交到dispatch_queue_t queue中执行。

    4、dispatch_group_wait

    long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
    

    个人理解:和dispatch_group_notify功能类似(多了一个dispatch_time_t参数可以设置超时时间),在group上任务完成前,dispatch_group_wait会阻塞当前线程(所以不能放在主线程调用)一直等待;当group上任务完成,或者等待时间超过设置的超时时间会结束等待;

    二、dispatch_group_async代码示例

    - (void)groupSync
    {
        dispatch_queue_t disqueue =  dispatch_queue_create("com.shidaiyinuo.NetWorkStudy", DISPATCH_QUEUE_CONCURRENT);
        dispatch_group_t disgroup = dispatch_group_create();
        dispatch_group_async(disgroup, disqueue, ^{
            
            NSLog(@"任务一完成");
        });
        
        dispatch_group_async(disgroup, disqueue, ^{
            
            sleep(8);
            NSLog(@"任务二完成");
        });
        
        dispatch_group_notify(disgroup, disqueue, ^{
            
            NSLog(@"dispatch_group_notify 执行");
        });
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            dispatch_group_wait(disgroup, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
            NSLog(@"dispatch_group_wait 结束");
        });
    }
    

    向group中放入两个任务(准确讲是将任务加入到了并行队列disqueue中执行,然后队列和group关联当队列上任务执行完毕时group会进行同步),第二个任务会等待8秒所以第一个任务会先完成;会先打印任务一完成再打印任务二完成,当两个任务都完成时dispatch_group_notify中的block会执行;会接着打印dispatch_group_notify 执行;dispatch_group_wait设置了超时时间为5秒所以它会在5秒后停止等待打印dispatch_group_wait 结束(任务二会等待8秒所以它会在任务二完成前打印);

    测试输出结果
    需要注意的:dispatch_group_wait是同步的所以不能放在主线程执行。
    补充: dispatch_group会等和它关联的所有的dispatch_queue_t上的任务都执行完毕才会发出同步信号(dispathc_group_notify的代码块block会被执行,group_wati会结束等待)。也就是说一个group可以关联多个任务队列;下面给出示例:
    - (void)groupSync2
    {
        dispatch_queue_t dispatchQueue = dispatch_queue_create("ted.queue.next1", DISPATCH_QUEUE_CONCURRENT);
        dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
        dispatch_group_t dispatchGroup = dispatch_group_create();
        dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
            
            sleep(5);
            NSLog(@"任务一完成");
        });
        dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
            
            sleep(6);
            NSLog(@"任务二完成");
        });
        
        dispatch_group_async(dispatchGroup, globalQueue, ^{
            
            sleep(10);
            NSLog(@"任务三完成");
        });
        
        dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
            NSLog(@"notify:任务都完成了");
        });
    }
    

    上面的代码里有两个队列一个是我自己创建的并行队列dispatchQueue,另一个是系统提供的并行队列globalQueue;dispatch_group_notify会等这两个队列上的任务都执行完毕才会执行自己的代码块。

    多个队列执行结果

    三、dispatch_group_enter、dispatch_group_level示例

    和dispatch_async相比,当我们调用n次dispatch_group_enter后再调用n次dispatch_group_level时,dispatch_group_notify和dispatch_group_wait会收到同步信号;这个特点使得它非常适合处理异步任务的同步当异步任务开始前调用dispatch_group_enter异步任务结束后调用dispatch_group_leve
    下面是代码示例:

    - (void)groupSync
    {
        dispatch_group_t group = dispatch_group_create();
        dispatch_group_enter(group);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
           
            sleep(5);
            NSLog(@"任务一完成");
            dispatch_group_leave(group);
        });
        dispatch_group_enter(group);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            sleep(8);
            NSLog(@"任务二完成");
            dispatch_group_leave(group);
        });
        dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
            
            NSLog(@"任务完成");
        });
    }
    

    示例代码中在global_queue上执行sleep任务模拟网络请求。

    控制台打印结果
    补充: 如果像最后一个示例那样,block里执行的是同步类型的代码那么用dispatch_group_async一样可以达到同步的效果,但是异步任务就不行了如下:
    - (void)groupSync2
    {
        dispatch_queue_t dispatchQueue = dispatch_queue_create("ted.queue.next1", DISPATCH_QUEUE_CONCURRENT);
        dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
        dispatch_group_t dispatchGroup = dispatch_group_create();
        dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
            
            dispatch_async(globalQueue, ^{
               
                sleep(5);
                NSLog(@"任务一完成");
            });
        });
        dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
            
            dispatch_async(globalQueue, ^{
                
                sleep(8);
                NSLog(@"任务二完成");
            });
        });
        dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
            NSLog(@"notify:任务都完成了");
        });
    }
    

    如果dispatch_group_async里执行的是异步代码dispatch_group_notify会直接触发而不会等待异步任务完成,而dispatch_group_enter、和dispatch_group_leave则不会有这个问题,它们只需要在任务开始前enter结束后leave即可达到线程同步的效果。

    相关文章

      网友评论

      • Specscd:dispatch_group_level -> dispatch_group_leave , 解决了notify和enter leave的疑惑
      • f34e80ac227a:其中提到并行队列,其实应该是并发队列,并行和并发还是有区别的。
      • Marco_Liu:原理分析跟源码有一些出入,建议还是去通读一下源码
        liang1991:@_小白龙 嗯当时写这篇文章更多的还是出于使用层面上的,背后的实现原理并没有做深入理解分析。
      • isapem:感谢楼主, 这也是我一直困扰的问题
      • 韦恩时代:对dispatch_group_notify的解释不正确,接口定义中有说明
        ```
        * @param queue
        * The queue to which the supplied block will be submitted when the group
        * completes.
        ```
        这个函数中的queue参数,指的是,当group中所有队列执行完毕后,block要在哪个queue中执行
        liang1991:恩,写错了。感谢指出错误:smile:
      • Roy_Gao:清晰明了!必须收藏啊:clap:
      • 75b8ffd94017:for in {
        enter
        sdwebimage下载图片任务
        level
        }
        如果下载的很快的话 第二个任务还没加进去 , 第一个任务就完成了, 会重复调用 notify
        liang1991:@eeeeedc_ leave应该加载completed里面
        75b8ffd94017:@liang1991 dispatch_group_enter(group);

        [bagdeImage sd_setImageWithURL:[NSURL URLWithString:urlStr] placeholderImage:placeImage completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {

        bagdeImage.image = image;
        }];
        dispatch_group_leave(group);
        外层 是一个循环. 这个图片下载的太快了, 第二个任务还买enter 到group里, 第一个就 leave, 这个时候 count = 0 , 就回调了, 我把这个 dispatch_group_leave(group); 延迟0.2秒, 就不会出现这种情况
        liang1991:leave是写在sd下载图片任务完成的block回调里面吗?
      • 34ab9b9838b1:Mark
        liang1991:@xinyiheng518 恩,简单理解就是当enter、leave成对儿执行时,dispatch_group_notify会收到一个通知。
        asaBoat:很好的文章

        是这样完成同步么??
        dispatch_group_t group = dispatch_group_create();
        dispatch_group_enter(group);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(5);
        NSLog(@"任务一完成");
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(5);
        NSLog(@"任务一完成");
        dispatch_group_leave(group);
        });

        });
        dispatch_group_enter(group);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(8);
        NSLog(@"任务二完成");
        dispatch_group_leave(group);
        });
        dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任务完成");
        });
      • 1江春水:dispatch_group_notify 内的任务 放在 所有任务最前面呢?会出现什么样的效果呢
        无神:@under_control 好吧,和理解的完全不一样!按照他的结构设计,notify中的代码应该是最后执行才对!结果代码写在前面,它居然先执行了!
        1江春水:@无神 我试过了,是先执行的notify内的任务的
        无神:也是最后执行,dispatch_group_notify 代码块里的代码是整个group执行完成之后才执行的。
      • 87f4d6307fa4:楼主,如果一个10万个元素的数组,用线程并发,求最大数,给个思路
        liang1991:这个问题偏理论一些吧,我也没有类似的实际开发经验。大致说下我的思路吧,把数组分成n个小数组,每个数组开一个线程去遍历 1、只用一个变量保存最大值,每个线程在访问这个变量的时候加锁;2、分成的每份数组对应一个变量存储最大值,当计算结束后从这些值里再取出最大值。
      • 一个好人爱算数:博主讲的很好,但有一点感觉需要稍微纠正一下,group结束,并不是队列中的所有任务完成,而是关联到group中的所有任务完成,系统跟我们共用global queue,这里面的任务是不会全部完成的,而且我们也没有必要等待别人的任务完成,所以应该是任务队列中关联到group的任务完成,就会发出通知
        liang1991:恩是的,感谢指出错误。等有时间了改下:smile:
      • pentakill:如果按照这个方法这样写 - (void)groupSync,在最后完成任务的时候在线程里面刷新UI会很慢,要等待大概5-6秒界面才会出现,这里是否需要改成 dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"任务完成");
        });
        pentakill:嗯嗯,已经get了。。楼主有关于gcd处理并发数的帖子吗!?就是关于信号量那个
        liang1991:你要更新UI的话是应该在主队列上的
      • fd565ceeb15b:你好 我想问下,用你的方法按顺序实现网络请求,在实际项目中没有刷新界面,该怎么处理下。不能上图,这是我的代码,帮忙看下。(网络请求)那两个方法中是我自己封装的网络请求获得数据并以模型放到数组里。

        dispatch_group_t group = dispatch_group_create();
        dispatch_group_enter(group);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{

        [self getTitleData];//第一个网络请求
        dispatch_group_leave(group);
        });
        dispatch_group_enter(group);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{

        [self getValueData];//第二个网络请求
        dispatch_group_leave(group);
        });
        dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        [self.tableView reloadData];
        NSLog(@"任务完成");
        });
        liang1991:@清风l 那刷新的方法执行了吗,heightforrow、cellforrow什么的,是不是逻辑有问题..
        fd565ceeb15b:@liang1991 我试了,好像还是不行
        liang1991:@清风l 最后的notify里的队列改成main queue试试
      • IOSMan:最后代码加上dispatch_group_enter、dispatch_group_level,和前面不加,直接用dispatch_group_async,最后dispatch_group_notify。这效果是一样的啊
        IOSMan:@liang1991 谢谢大佬:sunglasses:
        liang1991:可以看下我补充的代码例子..
        liang1991:例子中用dispatch_async加sleep是为了模拟一个异步耗时任务,如果block里面执行的代码是同步的那用dispatch_group_async也是一样的,你可以在dispatch_group_async的block用dispatch_async加个sleep之类的代码,然后就能看出不同了。
      • hanxiao790:太厉害了,完美解决了问题。
      • coderLZ:第三点如果把sleep换成AFN网络请求就不对了
        liang1991:@会跳舞的蜗牛 :smile:
        coderLZ:@liang1991 恩恩,leave的位置我写错地方了:relieved:
        liang1991:可以的,你可以看下我demo里MoreRequestSyncViewController的实现。
      • 买了否冷_:你好 如果我想等第一个任务完成后 在执行第二个任务 怎么办??
        liang1991:@偷偷学很多东西 同时进行的?也就是说你无法决定它们的调用顺序吗?这样的话你可以在任务2里卡一个信号量等任务1任务完成后发送信号,再执行任务2..
        买了否冷_:@liang1991 不可以,它们是同事进行的
        liang1991:@偷偷学很多东西 你自己已经把答案说出来了。 就是第一个完了 再调用第二个🙃。。
      • Airsola:博主写的 简洁明了,赞~
        liang1991:@airsola :blush:
      • 一叶__知秋:感谢楼主分享解决了我一个页面五个请求的问题
        liang1991:@luo叶知秋 :blush:
      • bigParis:好!

      本文标题:使用dispatch_group来进行线程同步

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