多线程之-GCD

理解GCD一些概念

队列:串行队列,执行完一个任务再执行下一个任务;并发队列,可以多个任务同时执行;
执行方式:同步方式,不开启新线程,在主线程执行;异步方式,开启新线程(当队列是主队列时,不开新线程);
最佳任务执行方案:异步方式+并发队列
多线程的死锁问题:不同队列的任务在相互等待完成;
耗时的任务应该放到后台去运行;

GCD

GCD全称是Grand Central Dispatch,是一套纯C的API;
GCD会自动利用更多的CPU内核;
GCD会自动管理线程的生命周期,使用block块来执行任务;
常用;

队列和任务

在了解GCD之前,我们先来了解GCD的核心:队列和任务;

队列

队列是用来放置任务的,分为串行队列和并发队列;
串行队列:任务执行方式是一个接着一个,只有一个任务完成了才会执行下一个任务,一个app中的主队列就是一个特殊串行队列;
并发队列:任务执行方式可以在同一时间执行多个任务;

任务

任务,就是我们要执行的代码块,方式有异步async和同步sync;
异步函数,会开启新线程(当队列是主队列的时候,不会开启新线程);
同步函数,不会开启新线程,就是说会在当前主线程中执行任务,会阻塞当前主线程;

GCD的使用

GCD的使用主要是队列与任务之间相互组合;

异步+串行:会创建新的线程,一个任务完成后接着下一个任务;
异步+并行:开启新的线程,任务可以同时执行,任务同时开启数量取决于cpu的调度;
异步+主队列:没有开启新线程,串行执行任务;
同步+串行:不会开启新的线程,并且会阻塞主线程,直到该任务完成;
同步+并行:不会开启新的线程,会阻塞主线程,直到函数内任务执行完毕,并发队列失效;
同步+主队列:相互之间等待对方完成,会造成死锁,避免使用该组合;

队列创建

串行队列的创建有2种方式:

1
2
3
//DISPATCH_QUEUE_SERIAL,NULL
dispatch_queue_t queue2 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue3 = dispatch_queue_create("queue1", NULL);

并行队列创建:

1
2
3
4
5
6
7
8
//DISPATCH_QUEUE_CONCURRENT
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
//优先级的并发队列
dispatch_queue_t queue212 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
//DISPATCH_QUEUE_PRIORITY_DEFAULT 默认
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)

获取主队列,主队列是一个特殊的串行队列,更新UI等一系列操作都必须在主队列中进行:

1
dispatch_queue_t mainQueue = dispatch_get_main_queue();

配合同步函数和异步函数的使用

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
dispatch_queue_t serial1 = dispatch_queue_create("serial1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serial2 = dispatch_queue_create("serial2", NULL);

dispatch_queue_t concurrent = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t mainQueue1 = dispatch_get_main_queue();
NSLog(@"当前主线程 = %@",[NSThread currentThread]);
//异步+串行
dispatch_async(serial1, ^{
NSLog(@"异步+串行 = %@",[NSThread currentThread]);
});
//异步+并行
dispatch_async(concurrent, ^{
NSLog(@"异步+并行 = %@",[NSThread currentThread]);
});
//异步+主队列
dispatch_async(mainQueue1, ^{
NSLog(@"异步+主队列 = %@",[NSThread currentThread]);
});

//同步+串行
dispatch_sync(serial2, ^{
NSLog(@"同步+串行 = %@",[NSThread currentThread]);
});
//同步+并行
dispatch_sync(concurrent, ^{
NSLog(@"同步+并行 = %@",[NSThread currentThread]);
});
// //同步+主队列
// dispatch_sync(mainQueue1, ^{
// //造成死锁
// });

// 3.3,在子线程中同步+主队列不会造成死锁
dispatch_async(concurrent1, ^{
dispatch_sync(mainQueue, ^{
NSLog(@"不会崩溃");
});
});

2018-08-02 15:13:55.410236+0800 FDThread[5090:518486] 当前主线程 = <NSThread: 0x600003dbd640>{number = 1, name = main}
2018-08-02 15:13:55.410445+0800 FDThread[5090:518486] 同步+串行 = <NSThread: 0x600003dbd640>{number = 1, name = main}
2018-08-02 15:13:55.410467+0800 FDThread[5090:518529] 异步+串行 = <NSThread: 0x600003dfa180>{number = 3, name = (null)}
2018-08-02 15:13:55.410508+0800 FDThread[5090:518626] 异步+并行 = <NSThread: 0x600003d8f540>{number = 4, name = (null)}
2018-08-02 15:13:55.410670+0800 FDThread[5090:518486] 同步+并行 = <NSThread: 0x600003dbd640>{number = 1, name = main}
2018-08-02 15:13:55.442415+0800 FDThread[5090:518486] 异步+主队列 = <NSThread: 0x600003dbd640>{number = 1, name = main}

GCD中的死锁

1,在主线程中当使用同步+主队列的时候,会造成线程的死锁;
要避免死锁,队列不要使用主队列,换成其他串行队列或者并发队列;

1
2
3
4
5
6
7
8
9
10
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// NSLog(@"1");
// dispatch_sync(mainQueue, ^{
// NSLog(@"2");
// });
// NSLog(@"3");
//线程相互阻塞,死锁:队列已经分发在等待执行;
//主线程,串行队列:要执行3必须要先执行2
//同步,串行主队列:要执行2,必须要执行获取到主队列中3;
//最后导致线程相互等待;

2,在子线程中使用同步+主队列的时,不会造成死锁.

GCD之间的通讯

1,一个线程的数据传递给另外一个线程;
2,一个线程开始执行依赖另外一个线程;

例子:
1,当你使用GCD进行下载图片而有需要在主线程中进行UI界面更新的时候,会用到线程之间的通讯;
2,线程2开启依赖线程1任务是否执行完毕

1
2
3
4
5
6
7
8
dispatch_queue_t q1 = dispatch_queue_create("q1", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(q1, ^{
//dowload image

dispatch_async(dispatch_get_main_queue(), ^{
//update UI
});
});

GCD其他方法

延迟函数:dispatch_after

延迟2秒执行block内的任务,严格来说,这个时间不是绝对正确的;

1
2
3
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"late 2min");
});

dispatch_once

通常被用在单例的创建;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//只会被执行一次代码,是线程安全的
});
示例:
#import "ZYInfo.h"
static ZYInfo * _info = nil;
@implementation ZYInfo
#pragma mark -- GCD单例创建
+(ZYInfo *)shareInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_info = [[super allocWithZone:NULL]init];
});
return _info;
}
+(id)allocWithZone:(struct _NSZone *)zone{
return [ZYInfo shareInstance];
}
+(id)copyWithZone:(struct _NSZone *)zone{
return [ZYInfo shareInstance] ;
}

栅栏barrier

我们有时候需要异步来执行2组操作,但是却希望是第一组操作完成之后再执行另外一组;这个时候,我们就可以使用来barrier实现:

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
dispatch_queue_t concurrentBarrier = dispatch_queue_create("barrier", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(concurrentBarrier, ^{
for (int i = 0; i < 3; i ++) {
[NSThread sleepForTimeInterval:2];//模拟耗时操作
NSLog(@"1");
}
});
dispatch_async(concurrentBarrier, ^{
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];//模拟耗时操作
NSLog(@"2");
}
});

dispatch_barrier_sync(concurrentBarrier, ^{
NSLog(@"3");
});

dispatch_async(concurrentBarrier, ^{
NSLog(@"4");
});
2018-08-02 18:13:30.307159+0800 FDThread[1514:109487] 2
2018-08-02 18:13:30.307159+0800 FDThread[1514:109488] 1
2018-08-02 18:13:32.312161+0800 FDThread[1514:109488] 1
2018-08-02 18:13:32.312199+0800 FDThread[1514:109487] 2
2018-08-02 18:13:34.314149+0800 FDThread[1514:109488] 1
2018-08-02 18:13:34.314368+0800 FDThread[1514:109403] 3
2018-08-02 18:13:34.314510+0800 FDThread[1514:109488] 4

4是在1,2,3之后完成;

dispatch_apply

把一个任务提交到队列中多次执行,具体是串行还是并行由队列本身决定;
apply不会立即返回,在执行完毕之后才会返回,是同步的调用;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dispatch_queue_t concurrent = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"apply-begin");
dispatch_apply(6, concurrent, ^(size_t index) {
NSLog(@"%ld -- %@",index,[NSThread currentThread]);
});
NSLog(@"apply-end");
2018-08-03 10:25:47.157811+0800 FDThread[2873:199134] apply-begin
2018-08-03 10:25:47.157936+0800 FDThread[2873:199134] 0 -- <NSThread: 0x600003b50ec0>{number = 1, name = main}
2018-08-03 10:25:47.158042+0800 FDThread[2873:199134] 1 -- <NSThread: 0x600003b50ec0>{number = 1, name = main}
2018-08-03 10:25:47.158098+0800 FDThread[2873:199256] 2 -- <NSThread: 0x600003b229c0>{number = 3, name = (null)}
2018-08-03 10:25:47.158275+0800 FDThread[2873:199134] 3 -- <NSThread: 0x600003b50ec0>{number = 1, name = main}
2018-08-03 10:25:47.158356+0800 FDThread[2873:199258] 4 -- <NSThread: 0x600003b22a00>{number = 5, name = (null)}
2018-08-03 10:25:47.158385+0800 FDThread[2873:199259] 5 -- <NSThread: 0x600003b22840>{number = 4, name = (null)}
2018-08-03 10:25:47.159049+0800 FDThread[2873:199134] apply-end

可以看出,当队列是并发队列的时候,除了会在主线程中执行还会开启其他的线程;
当队列是串行队列的时候,只会在主线程执行,不会开启新的线程;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dispatch_queue_t serial1 = dispatch_queue_create("serial1", DISPATCH_QUEUE_SERIAL);
NSLog(@"apply-begin");
dispatch_apply(6, serial1, ^(size_t index) {
NSLog(@"%ld -- %@",index,[NSThread currentThread]);
});
NSLog(@"apply-end");

2018-08-03 10:41:44.982335+0800 FDThread[3031:209835] apply-begin
2018-08-03 10:41:44.982459+0800 FDThread[3031:209835] 0 -- <NSThread: 0x600002fd93c0>{number = 1, name = main}
2018-08-03 10:41:44.982570+0800 FDThread[3031:209835] 1 -- <NSThread: 0x600002fd93c0>{number = 1, name = main}
2018-08-03 10:41:44.982682+0800 FDThread[3031:209835] 2 -- <NSThread: 0x600002fd93c0>{number = 1, name = main}
2018-08-03 10:41:44.982981+0800 FDThread[3031:209835] 3 -- <NSThread: 0x600002fd93c0>{number = 1, name = main}
2018-08-03 10:41:44.983332+0800 FDThread[3031:209835] 4 -- <NSThread: 0x600002fd93c0>{number = 1, name = main}
2018-08-03 10:41:44.983653+0800 FDThread[3031:209835] 5 -- <NSThread: 0x600002fd93c0>{number = 1, name = main}
2018-08-03 10:41:44.983951+0800 FDThread[3031:209835] apply-end

dispatch_group

有时候,我们会有这样的需求:分别要求执行2个耗时任务,在这2个耗时的任务完成之后,我们在回到主线程或者指定线程来进行下一步的操作,这个时候就需要用到GCD中的队列组;

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
dispatch_group_t group = dispatch_group_create();
NSLog(@"group_begin");
dispatch_group_async(group, concurrent, ^{
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];//模拟延时操作
NSLog(@"group1");
}
});
dispatch_group_async(group, serial2, ^{
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];//模拟延时操作
NSLog(@"group2");
}
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//等队列组内操作完成后,回到指定线程中进行下一步操作
NSLog(@"group_end");
});

2018-08-03 11:10:38.731557+0800 FDThread[3334:233506] group_begin
2018-08-03 11:10:40.733320+0800 FDThread[3334:233602] group2
2018-08-03 11:10:40.733355+0800 FDThread[3334:233599] group1
2018-08-03 11:10:42.737682+0800 FDThread[3334:233599] group1
2018-08-03 11:10:42.737677+0800 FDThread[3334:233602] group2
2018-08-03 11:10:42.738053+0800 FDThread[3334:233506] group_end

dispatch_group的enter和leave

使用达到上面相同效果,他们的使用必须要是配对出现

1
2
dispatch_group_enter(group);
dispatch_group_leave(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
dispatch_group_t group2 = dispatch_group_create();
dispatch_queue_t q22 = dispatch_get_global_queue(0, 0);

dispatch_group_enter(group2);
dispatch_async(q22, ^{
//task1
NSLog(@"enter1 = %@",[NSThread currentThread]);
dispatch_group_leave(group2);
});

dispatch_group_enter(group2);
dispatch_async(q22, ^{
//task2
NSLog(@"enter2 = %@",[NSThread currentThread]);
dispatch_group_leave(group2);
});

dispatch_group_notify(group2, dispatch_get_main_queue(), ^{
//执行task1和task2群组之后,才会执行这里面的任务
NSLog(@"enter3");
});

2018-08-07 15:28:48.754127+0800 FDThread[1480:55093] enter2 = <NSThread: 0x60000085e9c0>{number = 5, name = (null)}
2018-08-07 15:28:48.754163+0800 FDThread[1480:55095] enter1 = <NSThread: 0x60000085e680>{number = 4, name = (null)}
2018-08-07 15:28:48.778816+0800 FDThread[1480:55003] enter3

dispatch_wait

暂停当前线程(阻塞当前线程),等待指定的 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
dispatch_group_t group = dispatch_group_create();
NSLog(@"group_begin");
dispatch_group_async(group, concurrent, ^{
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];//模拟延时操作
NSLog(@"group1");
}
});
dispatch_group_async(group, serial2, ^{
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];//模拟延时操作
NSLog(@"group2");
}
});

// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// //等队列组内操作完成后,回到指定线程中进行下一步操作
// NSLog(@"group_end");
// });

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group_end");

2018-08-03 11:20:41.968222+0800 FDThread[3430:240751] group_begin
2018-08-03 11:20:43.973824+0800 FDThread[3430:240815] group1
2018-08-03 11:20:43.973805+0800 FDThread[3430:240809] group2
2018-08-03 11:20:45.975265+0800 FDThread[3430:240815] group1
2018-08-03 11:20:45.975284+0800 FDThread[3430:240809] group2
2018-08-03 11:20:45.975725+0800 FDThread[3430:240751] group_end