文章

GCD相关实用方法讲解

写在前面

gcd在使用上主要是为了多线程操作,及由解决暂用主线程导致UI界面卡顿的问题,其中主要就是同步线程和异步线程。
涉及到的应用方式常用的有如何有效的执行线程等待、多线程协同工作

1. dispatch_semaphore之停车入库原理

1.1 原理

semaphore 信号同步机制,可以简单以停车场停车入库来解释:
停车场有十个车位,现在即使来了十辆车也能全部进来停下,但是此时又来了一辆车,那么他就必须等待库里出来一辆车才行了。
我用普通话解释下上面的代码运行原理。
停车场当前有value个停车位(dispatch_semaphore_create(long value));
此时进入了一辆车(dispatch_semaphore_wait),剩余车位减少一个;
然后又出去一辆车(dispatch_semaphore_signal),剩余车位增加一个;
当剩余车位为0时,再来车(dispatch_semaphore_wait),就只能等待。来的车,等待的时间有两种:
一是给自己设定了一段等待时间(dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, 110001000*1000);//等待一秒),这段时间内等不到停车位就走了,如果等到了就开进去停车;
二是一直等下去(DISPATCH_TIME_FOREVER)。

semaphore的三个主要操作函数分别是:

  1. dispatch_semaphore_create 创建一个semaphore
  2. dispatch_semaphore_signal 发送一个信号
  3. dispatch_semaphore_wait 等待信号

1.2 dispatch_semaphore_create

1
dispatch_semaphore_t  dispatch_semaphore_create(long value); 

传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量。
值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL。

1.3 dispatch_semaphore_signal

1
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);

这个函数会使传入的信号量dsema的值加1;

1.4 dispatch_semaphore_wait

1
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

这个函数会使传入的信号量dsema的值减1;
这个函数的作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;
如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,
不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,
且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。
如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。

1.5 signal 和 wait

dispatch_semaphore_signal的返回值为long类型,当返回值为0时表示当前并没有线程等待其处理的信号量,其处理的信号量的值加1即可。当返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。
dispatch_semaphore_wait的返回值也为long型。当其返回0时表示在timeout之前,该函数所处的线程被成功唤醒。当其返回不为0时,表示timeout发生。

1.6 DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER

在设置timeout时,比较有用的两个宏:DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER。

  • DISPATCH_TIME_NOW  表示当前;
  • DISPATCH_TIME_FOREVER  表示遥远的未来;
    一般可以直接设置timeout为这两个宏其中的一个,或者自己创建一个dispatch_time_t类型的变量。
    创建dispatch_time_t类型的变量有两种方法,dispatch_time和dispatch_walltime。
    利用创建dispatch_time创建dispatch_time_t类型变量的时候一般也会用到这两个变量。
    dispatch_time的声明如下:
    1
    
      dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
    

    其参数when需传入一个dispatch_time_t类型的变量,和一个delta值。表示when加delta时间就是timeout的时间。
    例如:dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, 110001000*1000);
    表示当前时间向后延时一秒为timeout的时间。

1.7 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
dispatch_semaphore_t signal;
signal = dispatch_semaphore_create(1);
__block long x = 1;
NSLog(@"0_x:%ld",x);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(1);
    NSLog(@"waiting");
    x = dispatch_semaphore_signal(signal);
    NSLog(@"1_x:%ld",x);
        
    sleep(2);
    NSLog(@"waking");
    x = dispatch_semaphore_signal(signal);
    NSLog(@"2_x:%ld",x);
});
//    dispatch_time_t duration = dispatch_time(DISPATCH_TIME_NOW, 1*1000*1000*1000); //超时1秒
//    dispatch_semaphore_wait(signal, duration);
    
x = dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
NSLog(@"3_x:%ld",x);
    
x = dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
NSLog(@"wait 2");
NSLog(@"4_x:%ld",x);
    
x = dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
NSLog(@"wait 3");
NSLog(@"5_x:%ld",x);

输出结果为:

2017-08-08 17:14:33.238 Demo[10891:2002129] 0_x:1
2017-08-08 17:14:33.238 Demo[10891:2002129] 3_x:0
2017-08-08 17:14:34.240 Demo[10891:2002953] waiting
2017-08-08 17:14:34.241 Demo[10891:2002953] 1_x:1
2017-08-08 17:14:34.241 Demo[10891:2002129] wait 2
2017-08-08 17:14:34.241 Demo[10891:2002129] 4_x:0
2017-08-08 17:14:36.241 Demo[10891:2002953] waking
2017-08-08 17:14:36.241 Demo[10891:2002953] 2_x:1
2017-08-08 17:14:36.241 Demo[10891:2002129] wait 3
2017-08-08 17:14:36.241 Demo[10891:2002129] 5_x:0
并报出奔溃EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

dispatch_semaphore_t调用_dispatch_semaphore_dispose 释放时,当前信号量值必须大于等于初始信号量值时,才能正常释放,否则将引起EXC_BAD_INSTRUCTION指令错误。


2. dispatch_group之分组执行原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t serialQueue = dispatch_queue_create("com.wzb.test.www", DISPATCH_QUEUE_SERIAL);
dispatch_group_enter(group);
dispatch_group_async(group, serialQueue, ^{
    // 网络请求一
    [WebClick getDataSuccess:^(ResponseModel *model) {
        dispatch_group_leave(group);
    } failure:^(NSString *err) {
        dispatch_group_leave(group);
    }];
});
dispatch_group_enter(group);
dispatch_group_async(group, serialQueue, ^{
    // 网络请求二
    [WebClick getDataSuccess:getBigTypeRM onSuccess:^(ResponseModel *model) {
        dispatch_group_leave(group);
    } failure:^(NSString *errorString) {
        dispatch_group_leave(group);
    }];
});
dispatch_group_enter(group);
dispatch_group_async(group, serialQueue, ^{
    // 网络请求三
    [WebClick getDataSuccess:^{
        dispatch_group_leave(group);
    } failure:^(NSString *errorString) {
        dispatch_group_leave(group);
    }];
});
 
// 所有网络请求结束后会来到这个方法
dispatch_group_notify(group, serialQueue, ^{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            // 刷新UI
        });
    });
});

2.1 dispatch_group_create 创建组

1
dispatch_group_t dispatch_group_create(void);

用于创建任务组

2.2 dispatch_group_enter 进入组

1
void dispatch_group_enter(dispatch_group_t group);

用于添加对应任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数加1,当未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞和dispatch_group_notify的block执行(下面会讲解wait和notify的不同)

2.3 dispatch_group_leave 离开组

1
void dispatch_group_leave(dispatch_group_t group);

用于减少任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数减1,dispatch_group_enter和dispatch_group_leave要匹配,不然系统会认为group任务没有执行完毕

2.4 dispatch_group_async 将任务放入执行

1
2
3
void dispatch_group_async(dispatch_group_t group,
                          dispatch_queue_t queue,
                          dispatch_block_t block);

group 对应的任务组,之后可以通过dispatch_group_wait或者dispatch_group_notify监听任务组内任务的执行情况
queue block任务执行的线程队列,任务组内不同任务的队列可以不同
block 执行任务的block

2.5 dispatch_group_wait dispatch_group_notify

  • dispatch_group_wait <br > 等待组任务完成,会阻塞当前线程,当任务组执行完毕时,才会解除阻塞当前线程

    1
    
      long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
    

    group 需要等待的任务组
    timeout 等待的超时时间(即等多久),单位为dispatch_time_t。如果设置为DISPATCH_TIME_FOREVER,则会一直等待(阻塞当前线程),直到任务组执行完毕

  • dispatch_group_notify
    待任务组执行完毕时调用,不会阻塞当前线程

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

    group 需要监听的任务组
    queue block任务执行的线程队列,和之前group执行的线程队列无关
    block 任务组执行完毕时需要执行的任务block

参考文档

https://www.jianshu.com/p/792ebd0a79a7

本文由作者按照 CC BY 4.0 进行授权