Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。GCD 可用于多核的并行运算;GCD 会自动利用更多的 CPU 内核(比如双核、四核);GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);

同步&异步

同步和异步的概念主要区别:具不具备开启新的线程的能力,决定了任务在哪一个线程执行了;但更确切的说同步(sync)和 异步(async)的区别在于会不会阻塞当前线程,直到 Block 中的任务执行完毕;同步(sync) 操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行。异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程;

  • 同步:在当前线程中执行任务,不具备开启新线程的能力;

    1
    dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
  • 异步:在新的线程中执行任务,具备开启新线程的能力;

    1
    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

任务&队列

任务:即具体要干的事情要执行的操作,一般是一段代码块,在 GCD 中是一个 就是一个 Block 块,这里需要特意留意下任务是 Block 块;任务存放于队列中,任务有两种执行方式:是串行还是并行;同步和异步决定任务在那条线程中执行;

队列:用于存放任务,GCD 中队列分为两大类 dispatch_queue_t,串行队列 和 并行队列,队列的类型决定了任务的执行方式;

  • 串行队列:放到串行队列的任务,任务会以 FIFO(先进先出)的方式地取出来,让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务),实际上串行队列中也只能开启一条新线程;其中特殊的是系统自身就已经创建了一个主队列,对应着主线程其也是串行队列,主线程的作用是处理UI事件(点击事件、滚动事件、拖拽事情),串行队列的创建及获取有如下方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 主队列 - 特殊的串行队列
    dispatch_queue_t queue = dispatch_get_main_queue();

    // 创建队列 - 串行队列
    /**
    创建队列

    第一个参数: "com.zerocc.testQueue" 队列名 可用于 Instruments 的调试和 CrashLog 查看
    第二个参数: NULL 指定为 NULL 或 DISPATCH_QUEUE_SERIAL,生成 Serial Dispatch Queue;
    指定为 DISPATCH_QUEUE_CONCURRENT,生成 Concurrent Dispatch Queue。
    返回值: 队列,dispatch_queue_t 类型
    */
    dispatch_queue_t queue = dispatch_queue_create("com.zerocc.test00Queue", NULL);

    dispatch_queue_t queue = dispatch_queue_create("com.zerocc.test01Queue", DISPATCH_QUEUE_SERIAL);
  • 并发队列:放到并发队列的任务,任务也是以 FIFO 的方式取出来,但不同的是,它取出来一个就会放到一个线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起并发执行的;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 全局并发队列
    /**
    获取全局并发队列

    第一个参数:用于指定队列优先级 一般使用默认优先级即可 DISPATCH_QUEUE_PRIORITY_DEFAULT 0;其它三种有:
    DISPATCH_QUEUE_PRIORITY_HIGH 2,
    DISPATCH_QUEUE_PRIORITY_LOW -2,
    DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 2 后台优先级
    第二个参数:苹果预留设计传入0即可 0
    返回值: 全局并发队列
    */
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.zerocc.test02Queue", DISPATCH_QUEUE_CONCURRENT);

总结:

  • dispatch_sync 同步执行不管任务是在串行队列还是在并行队列,任务都是在当前线程一个接一个执行(主队列特例除外例如子线程中调用 sync + 主队列那么任务会从子线程回到主线程执行) - apple documentation Discussion
  • dispatch_async 异步执行任务放在串行队列,任务在其它线程一个接一个执行;任务放在并行队列,任务在多个线程中并发执行;

GCD 常用方法

dispatch_once (GCD 一次性代码执行)

dispatch_once 能保证 Block 中代码块在程序运行过程只执行一次,常用于单例的创建;并且在多线程的环境下,dispatch_once 可以保证线程安全;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (int i=0; i<3; i++) {
[self disaptchOnce];
}
}

- (void)disaptchOnce {
NSLog(@"%s", __func__);

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"dispatch_once - 执行任务");
});
}
1
2
3
4
5
// 点击后打印结果如下, disaptchOnce 方法调用了三次,其中 dispatch_once 的 block 代码块只执行一次
... CCBlogCode[41182:17902797] -[CCGCDVC disaptchOnce]
... CCBlogCode[41182:17902797] dispatch_once - 执行任务
... CCBlogCode[41182:17902797] -[CCGCDVC disaptchOnce]
... CCBlogCode[41182:17902797] -[CCGCDVC disaptchOnce]

dispatch_group(GCD 队列组)

队列组可以将多个队列添加到一个组里,从而管理监听所有任务都执行完成情况,当所有任务都执行完成有一个回调监听。这里的多个队列可以是不同的;运用场景可以是多个网络请求的控制等例如表单类型填写上报优先级问题;实现步骤如下:

  • 创建队列组,通过调用 dispatch_group_create()方法创建得到 dispatch_group_t 类型 group;
  • 将队列中的任务添加到队列组 group 中, 通过 dispatch_group_async(队列组,队列,block) 方法;次步也可以使用 dispatch_group_enterdispatch_group_leave 这两个组合替代 dispatch_group_async, dispatch_group_enter:表示将一个队列中任务放入 group 队列组中,此时 group 队列组任务数加1, dispatch_group_leave:表示将一个队列中任务从 group 队列组撤出,此时 group 队列组任务数减1;一定是配合一对一使用否则 group 任务数不为0(也就是标志是否执行完),任务数不为0则不会走下一步的监听回调 Block;
  • 监听所有队列中任务完成的回调,具体任务完成回调监听方法 dispatch_group_notify(队列组,队列,block)
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
// GCD 队列组常规使用
- (void)dispatchGroup {

// 创建一个并发队列
dispatch_queue_t queue = dispatch_queue_create("com.zerocc.testQueue", DISPATCH_QUEUE_CONCURRENT);
// 1. 创建队列组
dispatch_group_t group = dispatch_group_create();

// 2. 将队列中的任务添加到队列组 group 中
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:5];
NSLog(@"任务1---%@", [NSThread currentThread]);
}
});

dispatch_group_async(group, queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"任务2---%@", [NSThread currentThread]);
}
});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"任务3---%@", [NSThread currentThread]);
}
});

// 3. 监听到所有队列中任务已完成
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"任务123都执行完毕---%@",[NSThread currentThread]);
});
}
1
2
3
4
5
6
7
8
9
10
11
// 执行结果打印
... CCBlogCode[1435:621582] 任务2---<NSThread: 0x1cc07f3c0>{number = 3, name = (null)}
... CCBlogCode[1435:621582] 任务2---<NSThread: 0x1cc07f3c0>{number = 3, name = (null)}
... CCBlogCode[1435:621582] 任务2---<NSThread: 0x1cc07f3c0>{number = 3, name = (null)}
... CCBlogCode[1435:621584] 任务3---<NSThread: 0x1c4263e80>{number = 4, name = (null)}
... CCBlogCode[1435:621584] 任务3---<NSThread: 0x1c4263e80>{number = 4, name = (null)}
... CCBlogCode[1435:621584] 任务3---<NSThread: 0x1c4263e80>{number = 4, name = (null)}
... CCBlogCode[1435:621581] 任务1---<NSThread: 0x1c807db00>{number = 5, name = (null)}
... CCBlogCode[1435:621581] 任务1---<NSThread: 0x1c807db00>{number = 5, name = (null)}
... CCBlogCode[1435:621581] 任务1---<NSThread: 0x1c807db00>{number = 5, name = (null)}
... CCBlogCode[1435:621451] 任务123都执行完毕---<NSThread: 0x1d007c4c0>{number = 1, name = main}
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
// GCD 队列组 dispatch_group_enter 和 dispatch_group_leave 配合使用
- (void)dispatchGroupEnterAndleave
{
NSLog(@"currentThread---%@",[NSThread currentThread]);

dispatch_queue_t queue = dispatch_queue_create("com.zerocc.testQueue", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_t group = dispatch_group_create();

// 队列1任务1添加进 group 中
dispatch_group_enter(group);
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:5];
NSLog(@"任务1---%@",[NSThread currentThread]);
}
dispatch_group_leave(group);
});

// 队列2任务2添加进 group 中
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1];
NSLog(@"任务2---%@",[NSThread currentThread]);
}
dispatch_group_leave(group);
});

// enter 和 leave 一定是成对的,例如任务2中 少了enter则执行完任务2直接走下面block回调,执行完任务1后会出现崩溃;
// 如果少了leave则执行完任务1和2后不走下面block回调
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"任务12都执行完毕---%@",[NSThread currentThread]);
});

}
1
2
3
4
5
6
7
// 执行结果打印
... CCBlogCode[1467:622770] currentThread---<NSThread: 0x1d406f940>{number = 1, name = main}
... CCBlogCode[1467:622862] 任务2---<NSThread: 0x1c806c540>{number = 3, name = (null)}
... CCBlogCode[1467:622862] 任务2---<NSThread: 0x1c806c540>{number = 3, name = (null)}
... CCBlogCode[1467:622858] 任务1---<NSThread: 0x1c406cf40>{number = 4, name = (null)}
... CCBlogCode[1467:622858] 任务1---<NSThread: 0x1c406cf40>{number = 4, name = (null)}
... CCBlogCode[1467:622770] 任务12都执行完毕---<NSThread: 0x1d406f940>{number = 1, name = main}

队列组中还有一个方法 dispatch_group_wait(队列组, DISPATCH_TIME_FOREVER),可以用来阻塞当前线程,这里指的当前线程为队列组 group 所在的线程。

dispatch_after(GCD 延时执行)

GCD 延时执行就是针对某个队列中的任务 (block 块中)延迟执行,通过调用方法 dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block) 来指定某个任务在延迟特定的时间后再执行;

1
2
3
4
5
6
7
- (void)dispatchAfter {

// 主队列中任务3.0秒后延迟执行,这里也可以换成其它队列
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"延迟2秒执行---%@",[NSThread currentThread]);
});
}

dispatch_apply(GCD 快速迭代)

GCD 快速迭代类似于 for 和 while 循环遍历,但是不同的是可以将执行任务代码块添加到指定队列中进行;如果任务在串行队列中执行使用 dispatch_apply 效果等同于一般 for 循环,如果是并发队列进行异步执行的话,dispatch_apply 可以在多个线程同时异步遍历执行,会自动开启新线程执行任务,并且执行的顺序是不固定的。

1
2
3
4
5
6
7
8
9
10
11
12
13
// GCD 快速迭代 并发队列为例
- (void)dispatchApply {
NSLog(@"迭代---begin");

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(10, queue, ^(size_t index) {
[NSThread sleepForTimeInterval:2];
NSLog(@"迭代%zd---%@",index, [NSThread currentThread]);
});

NSLog(@"迭代---end");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// 执行结果打印
... CCBlogCode[2001:814061] 迭代---begin
... CCBlogCode[2001:814061] 迭代0---<NSThread: 0x1d4260780>{number = 1, name = main}
... CCBlogCode[2001:814193] 迭代1---<NSThread: 0x1c807e000>{number = 3, name = (null)}
... CCBlogCode[2001:814426] 迭代3---<NSThread: 0x1c426c280>{number = 5, name = (null)}
... CCBlogCode[2001:814454] 迭代5---<NSThread: 0x1c0263fc0>{number = 7, name = (null)}
... CCBlogCode[2001:814453] 迭代4---<NSThread: 0x1cc07e340>{number = 6, name = (null)}
... CCBlogCode[2001:814425] 迭代2---<NSThread: 0x1c02642c0>{number = 4, name = (null)}
... CCBlogCode[2001:814061] 迭代6---<NSThread: 0x1d4260780>{number = 1, name = main}
... CCBlogCode[2001:814426] 迭代8---<NSThread: 0x1c426c280>{number = 5, name = (null)}
... CCBlogCode[2001:814193] 迭代7---<NSThread: 0x1c807e000>{number = 3, name = (null)}
... CCBlogCode[2001:814454] 迭代9---<NSThread: 0x1c0263fc0>{number = 7, name = (null)}
... CCBlogCode[2001:814061] 迭代---end

dispatch_source(GCD 实现定时器)

dispatch_source… 的使用,其中通过调用方法dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)创建事件,第一个参数为 dispatch_source_type_t 类型,其代表指定事件类型,可以自定义事件或者 Timer 事件还有 Mach 端口相关事件等等,可以 command 点进去看API了;此文只对定时器事件运用分析。

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
40
41
42
43
44
45
46
47
48
49
50
51
52
- (void)dispatchSource {
__block int timeout = 15;

//获取全局队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

/**
1. 创建GCD中的定时器,并将定时器的任务交给全局并发队列执行(不会造成主线程阻塞)

第一个参数: DISPATCH_SOURCE_TYPE_TIMER source的类型 DISPATCH_SOURCE_TYPE_TIMER 表示是定时器
第二个参数: 0
第三个参数: 0
第四个参数: queue 队列
返回值: timer dispatch_source_t 类型
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

// 2. 设置定时器(timer 定时器,起始时间,间隔时间,精准度); 1.0 * NSEC_PER_SEC 时间单位为纳秒,设置定时器触发的时间间隔为1s; 0 * NSEC_PER_SEC 精确度 0s
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);

// block内部 使用弱引用修饰 避免循环调用
__weak typeof(self)weakSelf = self;
weakSelf.timerBtn.userInteractionEnabled = false;

// 3. 设置定时器的触发事件
dispatch_source_set_event_handler(timer, ^{
timeout--;

if (timeout > 0) {
// 回到主队列主线程设置 UI
dispatch_async(dispatch_get_main_queue(), ^{
NSString * title = [NSString stringWithFormat:@"%d秒后重新",timeout];
[weakSelf.timerBtn setTitle:title forState:UIControlStateNormal];
});
}else {
// 5. 取消定时器
dispatch_source_cancel(timer);
}
});

// 6. 取消定时器回调
dispatch_source_set_cancel_handler(timer, ^{
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.timerBtn.userInteractionEnabled = YES;
[weakSelf.timerBtn setTitle:@"重新获取" forState:UIControlStateNormal];
});
NSLog(@"定时取消");
});

// 4. 启动定时器
dispatch_resume(timer);
}

dispatch_barrier_async(GCD 栅栏方法)

dispatch_barrier_async作用就是分割其上下两部分任务执行过程,都是异步执行但是先执行其前面的任务先,再执行自身 Block 块中的任务,最后继续往下执行;表现形式就是分割作用。

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
- (void)dispatchBarrier {
dispatch_queue_t queue = dispatch_queue_create("zerocc.com.testQueue", DISPATCH_QUEUE_CONCURRENT);

// 任务1
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:3];
NSLog(@"任务1---%@",[NSThread currentThread]);
}
});

// 任务2
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"任务2---%@",[NSThread currentThread]);
}
});

// barrier 分割等待
dispatch_barrier_async(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2];
NSLog(@"barrier---%@",[NSThread currentThread]);
}
});

// 任务3
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"任务3---%@",[NSThread currentThread]);
}
});
}
1
2
3
4
5
6
7
8
9
// 执行结果打印
... CCBlogCode[30046:4604551] 任务2---<NSThread: 0x600001bc0300>{number = 3, name = (null)}
... CCBlogCode[30046:4604553] 任务1---<NSThread: 0x600001bc0900>{number = 4, name = (null)}
... CCBlogCode[30046:4604551] 任务2---<NSThread: 0x600001bc0300>{number = 3, name = (null)}
... CCBlogCode[30046:4604553] 任务1---<NSThread: 0x600001bc0900>{number = 4, name = (null)}
... CCBlogCode[30046:4604553] barrier---<NSThread: 0x600001bc0900>{number = 4, name = (null)}
... CCBlogCode[30046:4604553] barrier---<NSThread: 0x600001bc0900>{number = 4, name = (null)}
... CCBlogCode[30046:4604553] 任务3---<NSThread: 0x600001bc0900>{number = 4, name = (null)}
... CCBlogCode[30046:4604553] 任务3---<NSThread: 0x600001bc0900>{number = 4, name = (null)}

dispatch_semaphore(GCD 信号量)

GCD 信号量可以理解为一个计数器,用来控制线程并发访问的最大数量问题,保证线程同步等。信号量初始值为1,代表只允许当前线程访问资源,当信号量值小与等于零时当前线程就进入休眠等待状态。

  • 初始化信号量,调用 dispatch_semaphore_create(1) 方法初始化信号量值为1;
  • 判断信号量值决定其是否能往下继续执行,通过调用方法 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)。如果信号量的值大于0,则该方法所处的线程就会继续执行,并且信号量值 -1;如果信号量值 <= 0, 当前线程进入休眠等待状态,也可以通过控制第二个参数使其不一直处于等待状态,例如:DISPATCH_TIME_NOW+3 表示3秒后继续向下执行;
  • 一个任务执行完后记得使信号量的值加1,通过调用方法 dispatch_semaphore_signal信号值 +1,从而后面任务可以继续执行;
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
- (void)dispatchSemaphore {
// 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("com.zerocc.semaphore", DISPATCH_QUEUE_CONCURRENT);

// 1. 初始化信号量值为1
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

// 任务1
dispatch_async(queue, ^{
// 2. 判断信号量值, 信号量 > 0 继续执行并且信号量值 -1;信号量 <= 0, 当前线程进入休眠等待状态
// 这里信号量为0了,所以任务2要等待ing
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[NSThread sleepForTimeInterval:5];
NSLog(@"任务1---%@",[NSThread currentThread]);
// 3. 信号量值 +1
dispatch_semaphore_signal(semaphore);
});

// 任务2
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
for (int i = 0; i < 3; ++i) {
[NSThread sleepForTimeInterval:3];
NSLog(@"任务2---%@",[NSThread currentThread]);
}
dispatch_semaphore_signal(semaphore);
});

// 任务3
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
for (int i = 0; i < 3; ++i) {
[NSThread sleepForTimeInterval:1];
NSLog(@"任务3---%@",[NSThread currentThread]);
}
dispatch_semaphore_signal(semaphore);
});
}
1
2
3
4
5
6
7
8
// 运行结果打印, 任务123先后顺序并不确定,但是是连续的,也就是说一定是保证只有一个线程执行任务,其它线程则是等待状态
... CCBlogCode[1688:347237] 任务1---<NSThread: 0x600003f3d9c0>{number = 3, name = (null)}
... CCBlogCode[1688:347235] 任务2---<NSThread: 0x600003f3f1c0>{number = 4, name = (null)}
... CCBlogCode[1688:347235] 任务2---<NSThread: 0x600003f3f1c0>{number = 4, name = (null)}
... CCBlogCode[1688:347235] 任务2---<NSThread: 0x600003f3f1c0>{number = 4, name = (null)}
... CCBlogCode[1688:347234] 任务3---<NSThread: 0x600003f3dc80>{number = 5, name = (null)}
... CCBlogCode[1688:347234] 任务3---<NSThread: 0x600003f3dc80>{number = 5, name = (null)}
... CCBlogCode[1688:347234] 任务3---<NSThread: 0x600003f3dc80>{number = 5, name = (null)}