5.dispatch_group实现多线程的同步

前言:

在日常的开发中,肯定会经常遇到这样的场景,一个页面中需要同时去调用好几个接口,等这几个接口调用完成之后,再次执行一些事情(比如说刷新页面reloadData,启用Button等等),像这种多线程的同步有很多种解决办法:例如写几个标识,每一个接口调用完就TRUE一个,当所有的都是TRUE之后,再去进行操作;用NSOperationQueue也可以。今天我这里说的是利用GCD中的dispatch_group调度组实现。如果不知道GCD是什么,那你就先去问问度娘。

实现这个场景,同学你需要掌握以下几个技能

1. dispatch_group_async
dispatch_group_async(dispatch_group_t ,
                         dispatch_queue_t ,
                         ^(void)block)

解读这个方法:将这个代码块block加到队列dispatch_queue_t中,并且与调用组dispatch_group_t关联。
说白了就是在调用组dispatch_group_t和队列dispatch_queue_t中执行代码块block,当代码块block执行完毕后,会调用dispatch_group_notify方法,同时dispatch_group_wait会执行。

2. dispatch_group_notify
dispatch_group_notify(dispatch_group_t ,
                      dispatch_queue_t,
                      ^(void)block)

解读这个方法:dispatch_queue_t中所有任务执行完毕后,会执行这个代码块block
我们一般就是在这个方法中去做那件最后要做的事情。

3. dispatch_group_enter
dispatch_group_enter(dispatch_group_t  )

解读这个方法:enter就是进入到某个调用组dispatch_group_t中,enter的出现就必须搭配leave
我个人把他理解成MRC中的retain关键字,给group引用计数+1

4. dispatch_group_leave
dispatch_group_leave(dispatch_group_t  )

解读这个方法:leave就是进入到某个调用组dispatch_group_t中,leave的出现就必须搭配enter
我个人把他理解成MRC中的release关键字,给group引用计数-1,当引用计数为0了之后就会调用dispatch_group_notify

5. dispatch_group_wait
dispatch_group_wait(dispatch_group_t  , dispatch_time_t )

解读这个方法:可以设置一个超时时间dispatch_time_t,意思就是wait会阻塞主线程,等待 dispatch_group中的任务执行,当执行完毕后,或者超过了dispatch_time_t设置的时间,就会结束这个方法,执行剩下的任务。

场景

我会用同一份代码,更改不同代码的位置来详细讲述上述的方法,请仔细区分
1
    dispatch_group_t serviceGroup = dispatch_group_create();
    ZDLog(@"开始了");
    dispatch_group_enter(serviceGroup);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(3);
        ZDLog(@"第一个任务开始了");
    });
    dispatch_group_wait(serviceGroup, dispatch_time(DISPATCH_TIME_NOW, 5 *NSEC_PER_SEC));
    ZDLog(@"等待");
    dispatch_group_leave(serviceGroup);
    dispatch_group_enter(serviceGroup);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(10);
        ZDLog(@"第二个任务开始了");
        dispatch_group_leave(serviceGroup);
    });
    dispatch_group_notify(serviceGroup, dispatch_get_main_queue(), ^{
        ZDLog(@"全部都执行完了");
    });

控制台输出结果

测试输出结果的时间.png

解读:
代码执行打印开始了之后进入第一个entergroup的引用计数+1然后,在第一个子线程dispatch_async中执行sleep(3); ZDLog(@"第一个任务开始了");,主线程执行wait,这时页面会卡住,但是在3s时会打印第一个dispatch_async中的结果,5s后,打印ZDLog(@"等待");执行leavegroup的引用计数-1,之后进入第二个entergroup的引用计数又+1,进入第二个子线程dispatch_async中执行sleep(10); ZDLog(@"第二个任务开始了");,执行leavegroup的引用计数-1,引用计数为0,调用dispatch_group_notify(serviceGroup, dispatch_get_main_queue(), ^{ ZDLog(@"全部都执行完了"); });
要点:
执行dispatch_group_wait会阻塞主线程,影响wait的两个因素中,引用计数为0晚于dispatch_time_t,故wait依照dispatch_time_t执行。

2
    dispatch_group_t serviceGroup = dispatch_group_create();
    ZDLog(@"开始了");
    dispatch_group_enter(serviceGroup);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(3);
        ZDLog(@"第一个任务开始了");
        dispatch_group_leave(serviceGroup);
    });
    dispatch_group_wait(serviceGroup, dispatch_time(DISPATCH_TIME_NOW, 5 *NSEC_PER_SEC));
    ZDLog(@"等待");
    dispatch_group_enter(serviceGroup);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(10);
        ZDLog(@"第二个任务开始了");
        dispatch_group_leave(serviceGroup);
    });
    dispatch_group_notify(serviceGroup, dispatch_get_main_queue(), ^{
        ZDLog(@"全部都执行完了");
    });

控制台输出结果

测试输出结果的时间.png

解读:
代码执行打印开始了之后进入第一个entergroup的引用计数+1然后,在第一个子线程dispatch_async中执行sleep(3); ZDLog(@"第一个任务开始了");,主线程执行wait,这时页面会卡住,但是在3s时会打印第一个dispatch_async中的结果,同时执行leavegroup的引用计数-1wait解除,进入第二个entergroup的引用计数又+1,进入第二个子线程dispatch_async中执行sleep(10); ZDLog(@"第二个任务开始了");,执行leavegroup的引用计数-1,引用计数为0,调用dispatch_group_notify(serviceGroup, dispatch_get_main_queue(), ^{ ZDLog(@"全部都执行完了"); });
要点:
执行dispatch_group_wait会阻塞主线程,影响wait的两个因素中,引用计数为0早于dispatch_time_t,故wait依照引用计数为0执行。

3
    dispatch_group_t serviceGroup = dispatch_group_create();
    ZDLog(@"开始了");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        ZDLog(@"第一个任务开始了");
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(10);
        ZDLog(@"第二个任务开始了");
    });
    dispatch_group_notify(serviceGroup, dispatch_get_main_queue(), ^{
        ZDLog(@"全部都执行完了");
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_group_wait(serviceGroup, dispatch_time(DISPATCH_TIME_NOW, 5 *NSEC_PER_SEC));
        ZDLog(@"等待");
    });

控制台输出结果

测试输出结果的时间.png

解读:
因为没有enterleave的存在,所以代码会依次进行,结果就是第二个dispatch_async会比第一个dispatch_async晚执行sleep(10)
要点:在子线程中执行wait不会影响阻塞主线程。

4
    dispatch_group_t serviceGroup = dispatch_group_create();
    ZDLog(@"开始了");
    dispatch_group_enter(serviceGroup);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        ZDLog(@"第一个任务开始了");
        dispatch_group_leave(serviceGroup);
    });
    dispatch_group_enter(serviceGroup);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(10);
        ZDLog(@"第二个任务开始了");
        dispatch_group_leave(serviceGroup);
    });
    dispatch_group_notify(serviceGroup, dispatch_get_main_queue(), ^{
        ZDLog(@"全部都执行完了");
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_group_wait(serviceGroup, dispatch_time(DISPATCH_TIME_NOW, 5 *NSEC_PER_SEC));
        ZDLog(@"等待");
    });

控制台输出结果

测试输出结果的时间.png

解读:
因为没有 enterleave的存在,所以 dispatch_group_notify会在引用计数为 0之后才会执行,结果就是第二个 dispatch_async执行完后才会执行 dispatch_group_notify(serviceGroup, dispatch_get_main_queue(), ^{ ZDLog(@"全部都执行完了"); });,而最后一个子线程 dispatch_async中的 wait会在 5 *NSEC_PER_SEC会执行 ZDLog(@"等待");
要点:在子线程中执行 wait不会影响阻塞主线程。

今天就到这里了,欢迎交流分享~~~

你可能感兴趣的:(5.dispatch_group实现多线程的同步)