【iOS】多线程系列三 - GCD

GCD(Grand Central Dispatch),是有Apple公司开发的一个多核编程的解决方案,用以优化应用程序支持多核处理器,是基于线程模式之上执行并发任务。

一、GCD的基本概念

GCD是纯C语言的,提供了非常多并且强大的函数,比NSThread性能更好。

1.1. GCD的优势

  • 利用设备多核进行并行运算
  • 自动充分利用设备的CPU内核
  • 自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

1.2. 任务和队列

GCD中有2个核心的概念:

  • 任务: 执行什么操作
  • 队列: 用来存放任务

GCD会自动将队列中的任务取出,放到对应的线程中执行。任务的取出遵循队列的FIFO(First In First Out)原则(先进先出,后进后出)。

1.2.1. 执行任务

执行任务有两种方式:同步和异步

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

1
2
3
4
5
/*
* queue:队列
* block:任务
*/
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

异步: 可以在新的线程中执行任务,具备开启新线程的能力。

1
2
3
4
5
/*
* queue:队列
* block:任务
*/
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

1.2.2. 队列

队列的类型:并行和串行

并行: 可以让多个任务同时执行(自动开启多个线程同时执行任务),并发功能只有在异步函数下才有效。

串行: 顺序执行任务(一个任务执行完毕后,再执行下一个任务)。

创建队列有两种方式:

  1. 创建队列
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 定义
    /*
    * label:队列唯一标识符(一般格式:BundleID+功能描述)
    * attr:串行或并行(并行:DISPATCH_QUEUE_CONCURRENT,串行:DISPATCH_QUEUE_SERIAL)
    */
    dispatch_queue_create(const char *_Nullable label,
    dispatch_queue_attr_t _Nullable attr);

    // 使用
    dispatch_queue_t queue = dispatch_queue_create("com.idbeny.www.download", DISPATCH_QUEUE_CONCURRENT);

  1. 获取全局队列
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 定义
    /*
    * identifier:优先级(一般传默认优先级)
    * flags:预留参数,传0即可
    */
    dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);

    // 优先级(宏)
    #define DISPATCH_QUEUE_PRIORITY_HIGH 2
    #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
    #define DISPATCH_QUEUE_PRIORITY_LOW (-2)
    #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN

    // 使用
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

  1. 获取主队列
    主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会在主线程中串行执行。
    1
    2
    3
    4
    5
    // 定义
    dispatch_get_main_queue(void)

    // 使用
    dispatch_queue_t queue = dispatch_get_main_queue();

二、同步、异步、并发、串行

同步、异步、并行、串行。这四个专业术语非常容易混淆。

同步串行: 队列中的线程依次执行,并且主线程阻塞,等待任务的完成。

异步串行: 队列中的线程依次执行,同时主线程还在继续执行。

同步并行: 队列中的线程会一起执行,但是同一时段只能有一个线程执行其他线程等待,等所有任务执行完,主线程继续执行。

异步并行: 队列中的线程一起执行,主线程也会继续执行。

同步和异步区别: 是否能开启新的线程。

串行和并行区别: 执行的表现形式不同。串行是依次执行,只有当前线程结束之后,另一个线程才开启。而并行是所有任务一起执行。

2.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
- (void)asyncConcurrent {
dispatch_queue_t queue = dispatch_queue_create("com.idbeny.www.download", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"开始");
dispatch_async(queue, ^{
NSLog(@"任务1-%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务2-%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务3-%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务4-%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务5-%@", [NSThread currentThread]);
});
NSLog(@"结束");
}
/*
输出:
开始
结束
任务2-<NSThread: 0x600002b12e40>{number = 5, name = (null)}
任务1-<NSThread: 0x600002b18b80>{number = 3, name = (null)}
任务3-<NSThread: 0x600002b18840>{number = 4, name = (null)}
任务4-<NSThread: 0x600002b1c200>{number = 6, name = (null)}
任务5-<NSThread: 0x600002b12e40>{number = 5, name = (null)}
*/

异步并行,开启了多条子线程,并且任务没有按照顺序执行。

上面示例开了5个异步任务后GCD自动开启了5个子线程来执行任务,如果开启100个任务是不是会创建100个子线程?肯定不会的,开几条线程并不是任务的数量决定的,是GCD内部自动决定的。假如真的开了100个线程,估计CPU也会因此罢工。

把上面的示例代码修改为串行执行,看下任务执行顺序:

2.2. 异步串行

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
- (void)asyncSerial {
dispatch_queue_t queue = dispatch_queue_create("com.idbeny.www.download", DISPATCH_QUEUE_SERIAL);
NSLog(@"开始");
dispatch_async(queue, ^{
NSLog(@"任务1-%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务2-%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务3-%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务4-%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务5-%@", [NSThread currentThread]);
});
NSLog(@"结束");
}
/*
输出:
开始
结束
任务1-<NSThread: 0x600003a6cac0>{number = 5, name = (null)}
任务2-<NSThread: 0x600003a6cac0>{number = 5, name = (null)}
任务3-<NSThread: 0x600003a6cac0>{number = 5, name = (null)}
任务4-<NSThread: 0x600003a6cac0>{number = 5, name = (null)}
任务5-<NSThread: 0x600003a6cac0>{number = 5, name = (null)}
*/

异步串行,开启一条子线程,所有任务都在该线程顺序执行。

2.3. 同步并行

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
- (void)syncConcurrent {
dispatch_queue_t queue = dispatch_queue_create("com.idbeny.www.download", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"开始");
dispatch_sync(queue, ^{
NSLog(@"任务1-%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务2-%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务3-%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务4-%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务5-%@", [NSThread currentThread]);
});
NSLog(@"结束");
}
/*
输出:
开始
任务1-<NSThread: 0x600002b90940>{number = 1, name = main}
任务2-<NSThread: 0x600002b90940>{number = 1, name = main}
任务3-<NSThread: 0x600002b90940>{number = 1, name = main}
任务4-<NSThread: 0x600002b90940>{number = 1, name = main}
任务5-<NSThread: 0x600002b90940>{number = 1, name = main}
结束
*/

同步并行,没有开启子线程,所有任务都在主线程顺序执行。

2.4. 同步串行

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
- (void)syncSerial {
dispatch_queue_t queue = dispatch_queue_create("com.idbeny.www.download", DISPATCH_QUEUE_SERIAL);
NSLog(@"开始");
dispatch_sync(queue, ^{
NSLog(@"任务1-%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务2-%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务3-%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务4-%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务5-%@", [NSThread currentThread]);
});
NSLog(@"结束");
}
/*
输出:
开始
任务1-<NSThread: 0x6000009181c0>{number = 1, name = main}
任务2-<NSThread: 0x6000009181c0>{number = 1, name = main}
任务3-<NSThread: 0x6000009181c0>{number = 1, name = main}
任务4-<NSThread: 0x6000009181c0>{number = 1, name = main}
任务5-<NSThread: 0x6000009181c0>{number = 1, name = main}
结束
*/

同步串行,不会开启子线程,所有任务都在主线程顺序执行。

2.5. 主队列

示例代码(异步):

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
- (void)asyncMain {
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"开始");
dispatch_async(queue, ^{
NSLog(@"任务1-%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务2-%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务3-%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务4-%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务5-%@", [NSThread currentThread]);
});
NSLog(@"结束");
}
/*
输出:
开始
结束
任务1-<NSThread: 0x600000a5ca40>{number = 1, name = main}
任务2-<NSThread: 0x600000a5ca40>{number = 1, name = main}
任务3-<NSThread: 0x600000a5ca40>{number = 1, name = main}
任务4-<NSThread: 0x600000a5ca40>{number = 1, name = main}
任务5-<NSThread: 0x600000a5ca40>{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
- (void)syncMain {
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"开始");
dispatch_sync(queue, ^{
NSLog(@"任务1-%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务2-%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务3-%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务4-%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务5-%@", [NSThread currentThread]);
});
NSLog(@"结束");
}
// 输出:开始

同步任务,直接进入死锁,最终程序崩溃。

造成死锁的原因是:主线程在执行dispatch_sync函数的时候,由于任务需要同步执行,主队列让主线程先执行队列中的任务,这时候dispatch_sync函数还没有执行完,主线程不知道该执行哪个就造成了死锁。

解决死锁:把syncMain整个代码块放到子线程中执行。

1
2
3
4
5
6
7
8
9
10
11
[self performSelectorInBackground:@selector(syncMain) withObject:nil];
/*
输出:
开始
任务1-<NSThread: 0x6000001d4040>{number = 1, name = main}
任务2-<NSThread: 0x6000001d4040>{number = 1, name = main}
任务3-<NSThread: 0x6000001d4040>{number = 1, name = main}
任务4-<NSThread: 0x6000001d4040>{number = 1, name = main}
任务5-<NSThread: 0x6000001d4040>{number = 1, name = main}
结束
*/

三、GCD的基本使用

3.1. 线程通信

GCD线程通信使用起来比NSThread更加方便。

示例代码(下载图片):

1
2
3
4
5
6
7
8
9
10
11
12
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSString *imgURLStr = @"https://image.baidu.com/search/detail?ct=503316480&z=9&tn=baiduimagedetail&ipn=d&word=5k%E9%AB%98%E6%B8%85%E5%A4%A7%E5%9B%BE&step_word=&ie=utf-8&in=&cl=2&lm=-1&st=-1&hd=0&latest=0&copyright=0&cs=3388459890,4007020485&os=1361240164,2722658368&simid=0,0&pn=0&rn=1&di=69520&ln=787&fr=&fmq=1605533629706_R&fm=rs4&ic=0&s=undefined&se=&sme=&tab=0&width=0&height=0&face=undefined&is=0,0&istype=0&ist=&jit=&bdtype=0&spn=0&pi=0&gsm=0&hs=2&oriquery=%E5%A4%A7%E5%9B%BE&objurl=http%3A%2F%2Fimg.jk51.com%2Fimg_jk51%2F196318182.jpeg&rpstart=0&rpnum=0&adpicid=0&force=undefined";
NSURL *imgURL = [NSURL URLWithString:imgURLStr];
NSData *imgData = [NSData dataWithContentsOfURL:imgURL];
UIImage *img = [UIImage imageWithData:imgData];
// 刷新UI操作只能在主线程进行
// 此处使用同步和异步没有区别,因为是在子线程中执行的
dispatch_async(dispatch_get_main_queue(), ^{
self.imgView.image = img;
});
});

3.2. 单例模式

GCD提供dispatch_once函数,可以让代码在整个程序运行过程中只运行一次(一般应用在单例模式)。

关键代码:

1
2
3
4
5
6
7
8
9
10
11
static dispatch_once_t onceToken;
NSLog(@"before:%zd", onceToken);
dispatch_once(&onceToken, ^{
// 此处代码在整个程序生命周期中只会执行一次
NSLog(@"after:%zd", onceToken);
});
/*
输出:
before:0
after:768
*/

内部实现原理:静态变量由于内存地址是全局唯一的,每次程序运行到此处时会查看静态变量内存地址是否有值(判断是否为0),如果有值就不再执行,否则就会执行代码块。

单例模式可以保证在程序运行过程中,一个类只有一个实例(节约系统资源),但是单例多了也会造成浪费系统资源。

示例代码:

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
53
54
55
56
57
58
59
60
61
62
// DBDownloadManager.h
@interface DBDownloadManager : NSObject<NSCopying, NSMutableCopying>

+ (instancetype)manager;

@end

// DBDownloadManager.m
@implementation DBDownloadManager
// static保证了_instance在应用程序生命周期内不会被释放
static DBDownloadManager * _instance;

+ (instancetype)manager {
return [[self alloc] init];
}

// 重写alloc方法,保证永远只分配一次存储空间
// alloc方法内部调用allocWithZone:方法,所以只需要重写allocWithZone:方法
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
// 方式一
/*
@synchronized (self) {
if (!_instance) {
_instance = [super allocWithZone:zone];
}
return _instance;
}
*/

// 方式二
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}

- (id)copyWithZone:(NSZone *)zone {
return _instance;
}

- (id)mutableCopyWithZone:(NSZone *)zone {
return _instance;
}

@end

// 使用
DBDownloadManager *downloadManager1 = [[DBDownloadManager alloc] init];
DBDownloadManager *downloadManager2 = [[DBDownloadManager alloc] init];
DBDownloadManager *downloadManager3 = [DBDownloadManager manager];
DBDownloadManager *downloadManager4 = [downloadManager3 copy];
DBDownloadManager *downloadManager5 = [downloadManager3 mutableCopy];
NSLog(@"%@\n%@\n%@\n%@\n%@", downloadManager1, downloadManager2, downloadManager3, downloadManager4, downloadManager5);
/*
输出:
<DBDownloadManager: 0x600003e8c540>
<DBDownloadManager: 0x600003e8c540>
<DBDownloadManager: 0x600003e8c540>
<DBDownloadManager: 0x600003e8c540>
<DBDownloadManager: 0x600003e8c540>
*/

还有一种简洁式写法(偷懒):

1
2
3
4
5
6
7
+ (instancetype)manager {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}

这种写法有风险,如果用户通过alloc、copy、mutableCopy创建实例对象,对象的内存地址是不一样的。

3.3. 延迟执行

NSTimerNSObject(NSDelayedPerforming)都可以让代码延迟执行,GCD也有提供延迟执行函数dispatch_after

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
* 延迟执行函数
* when:时间
* queue:队列(决定block中的任务在哪个线程执行)
* block:任务
*/
dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
dispatch_block_t block);

/*
* 设置延迟时间
* when:周期(多久执行一次)
* delta:延迟时间(单位:纳秒)
*/
dispatch_time(dispatch_time_t when, int64_t delta);

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
NSLog(@"1");
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_after(time, queue, ^{
NSLog(@"hello");
});
NSLog(@"2");
/*
输出:
2017-09-17 15:46:38.244408+0800 NSThreadDemo[88369:708022] 1
2017-09-17 15:46:38.245245+0800 NSThreadDemo[88369:708022] 2
2017-09-17 15:46:41.245267+0800 NSThreadDemo[88369:708022] hello
*/

注意:dispatch_after是等延迟时间到了之后再把任务提交到队列(如果先提交到队列,就很难控制队列执行的时间)。

3.3. 快速迭代

GCD有提供类似for循环的迭代函数dispatch_apply

1
2
3
4
5
6
7
8
/*
* iterations:迭代次数(遍历次数)
* queue:队列(不要使用主队列,一般使用全局队列)
* size_t:遍历索引(类似for循环定义的变量i)
*/
dispatch_apply(size_t iterations,
dispatch_queue_t queue,
void (^block)(size_t));

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(5, queue, ^(size_t i) {
NSLog(@"i:%zd--%@", i, [NSThread currentThread]);
});
/*
输出:
i:1--<NSThread: 0x60000270f140>{number = 2, name = (null)}
i:3--<NSThread: 0x600002770640>{number = 3, name = (null)}
i:2--<NSThread: 0x60000277db00>{number = 6, name = (null)}
i:4--<NSThread: 0x60000270f140>{number = 2, name = (null)}
i:0--<NSThread: 0x600002734400>{number = 1, name = main}
*/

通过输出,发现GCD迭代函数会开启多条子线程和主线程一起执行任务。

如果把上面示例代码中的队列换成串行队列,和for循环就一样了。但是一定不能换成主队列,会形成死锁。

3.4. 栅栏

栅栏函数在实际开发中的应用场景主要是等待,比如网络请求C必须等到网络请求A和B请求完成后才能开始请求。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
dispatch_queue_t queue = dispatch_queue_create("com.idbeny.www.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任务1");
});
dispatch_async(queue, ^{
NSLog(@"任务2");
});
dispatch_async(queue, ^{
NSLog(@"任务3");
});
dispatch_async(queue, ^{
NSLog(@"任务4");
});
/*
输出:
任务1
任务3
任务4
任务2
*/

上面示例代码是很常见的异步并行,任务的执行是没有顺序的。有没有办法让任务1和任务2执行完成后再执行任务3和任务4?答案:使用栅栏函数。

栅栏函数:

1
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t 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
dispatch_queue_t queue = dispatch_queue_create("com.idbeny.www.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任务1");
});
dispatch_async(queue, ^{
NSLog(@"任务2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"栅栏");
});
dispatch_async(queue, ^{
NSLog(@"任务3");
});
dispatch_async(queue, ^{
NSLog(@"任务4");
});
/*
输出:
任务1
任务2
栅栏
任务4
任务3
*/

把栅栏放到对应位置,就会把相关任务一分为二,先执行栅栏前面的任务,后执行栅栏后面的任务。需要注意的是,栅栏本身不影响任务是并行还是串行。

警告:使用栅栏函数时,不能使用全局队列,否则会导致栅栏无效(栅栏会像同步/异步函数一样被放到队列中)。

3.5. 队列组

队列组是用来管理队列的,通过队列组可以在所有队列的任务完成后,再执行其他任务。

如果是一个队列,是可以通过栅栏函数实现基本功能的。如果是多个队列,栅栏函数就无法满足需求。

示例代码:

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();
dispatch_queue_t queue1 = dispatch_queue_create("com.idbeny.www.test1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("com.idbeny.www.test2", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue1, ^{
NSLog(@"任务1");
});
dispatch_group_async(group, queue1, ^{
NSLog(@"任务2");
});
dispatch_group_async(group, queue2, ^{
NSLog(@"任务3");
});
dispatch_group_async(group, queue2, ^{
NSLog(@"任务4");
});
dispatch_group_notify(group, queue1, ^{
NSLog(@"任务执行结束");
});
/*
输出:
任务2
任务1
任务3
任务4
任务执行结束
*/

队列组可以监听到所有队列执行完成的通知。dispatch_group_notify是异步执行的。

注意:dispatch_group_notify函数传入的队列参数指的是后面的代码块放到哪个线程中执行。如果是自定义的队列就在子线程中执行,如果是主队列就在主线程中执行。

dispatch_group_notify是如何知道队列组中任务执行状态的?

dispatch_group_async是下面代码的合成写法:

1
2
3
4
5
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"任务");
dispatch_group_leave(group);
});

dispatch_async必须放在dispatch_group_enter后面,在任务执行完成后必须执行dispatch_group_leave,否则dispatch_group_notify收不到通知。dispatch_group_enterdispatch_group_leave是成对使用。

3.6. 补充

dispatch_queue_createdispatch_get_global_queue的区别:

  • dispatch_get_global_queue在整个应用程序中本身是默认存在的,并且提供了优先级。dispatch_queue_create是开发人员从0开始去创建一个队列。

  • 在iOS6.0之前,GCD中只要使用了带createretain的函数在最后都要做release操作(防止内存泄漏),除了主队列和全局并发队列。ARC之后,就不需要手动释放了。

  • 使用栅栏函数时,官方明确规定只有和dispatch_queue_create创建的队列一起使用才有效(没有具体原因)。

参考资料:

  1. GCDAPI:https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html#//apple_ref/c/func/dispatch_queue_create

  2. libdispatch:http://www.opensource.apple.com/source/libdispatch/libdispatch-187.5