目录:
参考的博客:
iOS八股文(十一)多线程之GCD
iOS八股文(十二)GCD之函数和死锁源码浅析
iOS八股文(十三)GCD函数解析(栅栏、信号量、一次性、调度组)
[iOS开发]GCD
iOS多线程:『GCD』详尽总结
GCD简介
GCD
是异步执行任务的技术之一。将应用程序中记述的线程管理用的代码在系统级中实现
我们为什么要使用GCD
GCD
可用于多核的并行运算GCD
会自动利用更多的CPU内核GCD
会自动管理线程的生命周期- 程序员只需要告诉
GCD
想要执行什么任务,不需要编写任何线程管理的代码
GCD任务和队列
任务
任务就是执行操作,也就是我们在线程中执行的那段代码。在GCD
中是放在Block
中的。
具体对任务的执行有两种方式:同步执行
和异步执行
,主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力
- 同步执行 (
sync
)- 同步添加任务到指定的队列中,在添加的任务执行结束前,会一直等待,直到队列里面的任务完成之后再继续执行
- 只能在当前线程中执行任务,不具备开启新线程的能力
- 异步执行 (
async
)- 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务
- 可以在新的线程中执行任务,具备开启新线程的能力
一个简单的例子: 你要给小张和小王打电话,同步执行
就是:你打电话给小张的时候,不能打给小王,只有等到你给小张打完电话之后你才能打给小王(等待任务执行结束),并且只能用当前的电话(不具备开启新线程的能力)。异步执行
就是:你打电话给小张的时候,不用等着和小张结束(不用等待任务执行结束),还能同时打给小王。而且除了当前电话,你还可以使用其他的一个或多个电话(具备开启新线程的能力)。
注意: ==异步执行(async)==虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关。
队列(Dispatch Queue)
这里的队列指执行任务的等待队列,即用来存放任务的队列。 队列是一种特殊的线性表,采用 FIFO
(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
GCD
中有两种队列: 串行队列 和 并发队列。两者都符合FIFO(先进先出)的原则。
两者的主要区别是:执行顺序不同,以及开启线程数不同。
- 串行队列(
Serial Dispatch Queue
):
每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务) - 并发队列(
Concurrent Dispatch Queue
):
可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
注意: 并发队列 的并发功能只有在异步(dispatch_async
)方法下才有效。
GCD的使用步骤
GCD
的使用步骤很简单,只有两步:
- 创建一个队列(串行队列或并发队列)
- 将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)
下边来看看队列的创建方法 / 获取方法,以及任务的创建方法。
队列的创建方法 / 获取方法
可以使用 dispatch_queue_create
方法来创建队列。该方法需要传入两个参数:
- 第一个参数表示队列的唯一标识符,用于
DEBUG
,可为空。队列的名称推荐使用应用程序ID
这种逆序全程域名。 - 第二个参数用来识别是串行队列还是并发队列。
DISPATCH_QUEUE_SERIAL
表示串行队列DISPATCH_QUEUE_CONCURRENT
表示并发队列
// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
对于串行队列,GCD
默认提供了:主队列(Main Dispatch Queue
)。
- 所有放在主队列中的任务,都会放到主线程中执行
- 可使用
dispatch_get_main_queue()
方法获得主队列
主队列其实并不特殊。 主队列的实质上就是一个普通的串行队列,只是因为默认情况下,当前代码是放在主队列中的,然后主队列中的代码,又都会放到主线程中去执行,所以才造成了主队列特殊的现象
// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();
对于并发队列,GCD
默认提供了 全局并发队列(Global Dispatch Queue)
- 可以使用
dispatch_get_global_queue
方法来获取全局并发队列。需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT
。第二个参数暂时没用,用0
即可。
// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
任务的创建方法
GCD
提供了同步执行任务的创建方法 dispatch_sync
和异步执行任务创建方法 dispatch_async
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
虽然使用 GCD
只需两步,但是既然我们有两种队列(串行队列 / 并发队列),两种任务执行方式(同步执行 / 异步执行),那么我们就有了四种不同的组合方式。这四种不同的组合方式是:
- 同步执行 + 并发队列
- 异步执行 + 并发队列
- 同步执行 + 串行队列
- 异步执行 + 串行队列
实际上,刚才还说了两种默认队列:全局并发队列、主队列。全局并发队列可以作为普通并发队列来使用。但是当前代码默认放在主队列中,所以主队列很有必要专门来研究一下,所以我们就又多了两种组合方式。这样就有六种不同的组合方式了:
- 同步执行 + 主队列
- 异步执行 + 主队列
不同组合方式各有什么不同呢?
任务和队列不同组合方式的区别
我们先来考虑最基本的使用,也就是当前线程为 主线程 的环境下,不同队列+不同任务 简单组合使用的不同区别。暂时不考虑 队列中嵌套队列 的这种复杂情况:
主线程中,不同队列+不同任务简单组合的区别:
区别 | 并发队列 | 串行队列 | 主队列 |
---|---|---|---|
同步(sync) | 没有开启新线程 | 没有开启新线程,串行执行任务 | 死锁卡住不执行 |
异步(async) | 有开启新线程,并发执行任务 | 有开启新线程(1条),串行执行任务 | 没有开启新线程,串行执行任务 |
注意: 在主线程中调用主队列+同步执行会导致死锁问题。 这是因为 主队列中追加的同步任务 和 主线程本身的任务 两者之间相互等待,阻塞了 主队列,最终造成了主队列所在的线程(主线程)死锁问题,原因是造成了主线程等待主线程执行完之后才去执行,所以就会一直等待下去,例子如下:
而如果我们在 其他线程 调用 主队列+同步执行,则不会阻塞 主队列,自然也不会造成死锁问题。最终的结果是:不会开启新线程,串行执行任务,因为是其他线程等待主线程执行完之后才开始执行自己的线程,所以就只需要照常等待主线程的执行即可。
队列嵌套情况下,不同组合方式区别
除了上边提到的主线程中调用主队列+同步执行会导致死锁问题。实际在使用串行队列的时候,也可能出现阻塞串行队列所在线程的情况发生,从而造成死锁问题。这种情况多见于同一个串行队列的嵌套使用。
比如下面代码这样:在异步执行+串行队列的任务中,又嵌套了当前的串行队列,然后进行同步执行:
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 异步执行 + 串行队列
dispatch_sync(queue, ^{ // 同步执行 + 当前串行队列
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
});
我们可以看到执行上面的代码会导致 串行队列中追加的任务 和 串行队列中原有的任务 两者之间相互等待,阻塞了串行队列,最终造成了串行队列所在的线程(子线程)死锁问题。
主队列造成死锁也是基于这个原因,所以,这也进一步说明了主队列其实并不特殊。
队列中嵌套队列这种复杂情况,我们先抛出一个结论,后面再去讲
不同队列+不同任务 组合,以及 队列中嵌套队列使用的区别:
关于不同队列和不同任务的形象理解
假设我们现在有5
个人要穿过一个门禁,这个门禁总共有10
个入口,管理员可以决定同一时间打开几个门,可以决定同一时间让一个人单独通过还是多个人一起通过。默认情况下,管理员只开启一个入口,且一个通道一次仅仅通过一个人。
这个例子中,人当作任务,管理员当作系统,入口代表线程。
5个人表示5个任务
10个人表示10条线程
- 串行队列就是5个人排成一支长队
- 并发队列就是5个人拍成不止一支队伍,可能2,3队
- 同步任务就是管理员只开启了一个入口(当前线程)
- 异步任务就是管理员同时开启了多个入口(当前线程+新开的线程)
- 异步+并发: 管理员开了多个入口(假设为3个),5个人排成了多只队伍(假设3个队伍),这样5个人就可以3个人同时一起穿过门禁
- 同步+并发: 同步这就开了一个入口,虽然并发执行有多只队伍,但是一个入口一次只能通过1个人,所以只好一个一个的走过去,表现的结果就是顺利通过入口
所以换成GCD来说:
- 异步+并发:系统开启了多个线程,任务可以多个同时运行
- 同步+并发:系统默认开了一个主线程,没有开启子线程,虽然处于并发队列中,但也只能一个接一个执行
GCD 的基本使用
先来讲讲并发队列的两种执行方式
同步执行 + 并发队列
特点: 在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务(一个一个排队执行任务)
/**
* 同步执行 + 并发队列
* 特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
*/
- (void)syncConcurrent {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"syncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"syncConcurrent---end");
}
打印结果如下:
从 同步执行 + 并发队列
中可看到:
- 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(
同步执行
不具备开启新线程的能力)。 - 所有任务都是在打印的
syncConcurrent---begin
和syncConcurrent---end
之间执行的(同步任务 需要等待队列的任务执行结束) - 任务按顺序执行的。按顺序执行的原因:虽然
并发队列
可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务
不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务
需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。
异步执行 + 并发队列
特点: 可以开启多个线程,任务交替(同时)执行
/**
* 异步执行 + 并发队列
* 特点:可以开启多个线程,任务交替(同时)执行。
*/
- (void)asyncConcurrent {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"asyncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"asyncConcurrent---end");
}
运行结果如下:
在 异步执行 + 并发队列
中可以看出:
- 除了当前线程(主线程),系统又开启了
3
个线程,并且任务是交替/同时执行的。(异步执行
具备开启新线程的能力。且并发队列
可开启多个线程,同时执行多个任务)。 - 所有任务是在打印的
syncConcurrent---begin
和syncConcurrent---end
之后才执行的。说明当前线程没有等待,而是直接开启了新线程,在新线程中执行任务(异步执行
不做等待,可以继续执行任务)
注意: 如果我们在main
函数程序快要return 0
终止之前添加的这三个异步执行的任务,那么就有可能在截止程序终止的时候只完成了其一的任务
或其二的任务
或三个都完成
或没有一个完成
,且完成的任务的顺序是不确定。
接下来再来讲讲串行队列的两种执行方式。
同步执行 + 串行队列
特点: 不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
/**
* 同步执行 + 串行队列
* 特点:不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
*/
- (void)syncSerial {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"syncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"syncSerial---end");
}
运行结果如下:
在 同步执行 + 串行队列
可以看到:
- 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(
同步执行
不具备开启新线程的能力) - 所有任务都在打印的
syncConcurrent---begin
和syncConcurrent---end
之间执行(同步任务
需要等待队列的任务执行结束) - 任务是按顺序执行的(
串行队列
每次只有一个任务被执行,任务一个接一个按顺序执行)
异步执行 + 串行队列
特点: 会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务
/**
* 异步执行 + 串行队列
* 特点:会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。
*/
- (void)asyncSerial {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"asyncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"asyncSerial---end");
}
运行结果如下:
在 异步执行 + 串行队列
可以看到:
- 开启了一条新线程(
异步执行
具备开启新线程的能力,串行队列
只开启一个线程) - 所有任务是在打印的
syncConcurrent---begin
和syncConcurrent---end
之后才开始执行的(异步执行 不会做任何等待,可以继续执行任务) - 任务是按顺序执行的(
串行队列
每次只有一个任务被执行,任务一个接一个按顺序执行)
下边讲讲刚才我们提到过的:主队列
- 主队列:
GCD
默认提供的串行队列
- 默认情况下,平常所写代码是直接放在主队列中的
- 所有放在主队列中的任务,都会放到主线程中执行
- 可使用
dispatch_get_main_queue()
获得主队列
我们再来看看主队列的两种组合方式
同步执行 + 主队列
同步执行 + 主队列
在不同线程中调用结果也是不一样,在主线程中调用会发生死锁问题,而在其他线程中调用则不会死锁。
在主线程中调用 “同步执行 + 主队列”
特点: 互相等待卡住不可行
/**
* 同步执行 + 主队列
* 特点(主线程调用):互等卡主不执行。
* 特点(其他线程调用):不会开启新线程,执行完一个任务,再执行下一个任务。
*/
- (void)syncMain {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"syncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"syncMain---end");
}
运行结果:
在主线程中使用 同步执行 + 主队列
可以发现:
- 追加到主线程的
任务 1、任务 2、任务 3
都不再执行了,而且syncMain---end
也没有打印,在XCode 9
及以上版本上还会直接报崩溃。这是为什么呢?
这是因为我们在主线程中执行 syncMain
方法,相当于把 syncMain
任务放到了主线程的队列中。而 同步执行
会等待当前队列中的任务执行完毕,才会接着执行。那么当我们把 任务 1
追加到主队列中,任务 1
就在等待主线程处理完 syncMain
任务。而syncMain
任务需要等待 任务 1
执行完毕,才能接着执行,所以就形成了互相等待,造成死锁。
那么,现在的情况就是 syncMain 任务
和 任务 1
都在等对方执行完毕。这样大家互相等待,所以就卡住了,所以我们的任务执行不了,也就导致后面的程序都在等待被执行,结果就是由于一直在等待 syncMain---end
也没有打印
要是如果不在主线程中调用,而在其他线程中调用会如何呢?
在其他线程中调用 “同步执行 + 主队列”
特点: 不会开启新线程,执行完一个任务,再执行下一个任务
// 使用 NSThread 的 detachNewThreadSelector 方法会创建线程,并自动启动线程执行 selector 任务
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
/**
* 同步执行 + 主队列
* 特点(主线程调用):互等卡主不执行。
* 特点(其他线程调用):不会开启新线程,执行完一个任务,再执行下一个任务。
*/
- (void)syncMain {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"syncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"syncMain---end");
}
运行结果如下:
在其他线程中使用 同步执行 + 主队列
可看到:
- 所有任务都是在主线程(非当前线程)中执行的,没有开启新的线程(所有放在主队列中的任务,都会放到主线程中执行)。
- 所有任务都在打印在
syncConcurrent---begin
和syncConcurrent---end
之间执行(同步任务
需要等待队列的任务执行结束)。 - 任务是按顺序执行的(主队列是
串行队列
,每次只有一个任务被执行,任务一个接一个按顺序执行)。
为什么现在就不会卡住了呢?
因为syncMain 任务
放到了其他线程里,而 任务 1、任务 2、任务3
都在追加到主队列中,这三个任务都会在主线程中执行。syncMain 任务
在其他线程中执行到追加 任务 1
到主队列中,因为主队列现在没有正在执行的任务,所以,会直接执行主队列的 任务1
,等 任务1
执行完毕,再接着执行 任务 2、任务 3
。所以这里不会卡住线程,也就不会造成死锁问题。
异步执行 + 主队列
特点: 只在主线程中执行任务,执行完一个任务,再执行下一个任务。
/**
* 异步执行 + 主队列
* 特点:只在主线程中执行任务,执行完一个任务,再执行下一个任务
*/
- (void)asyncMain {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"asyncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"asyncMain---end");
}
运行结果如下:
在 异步执行 + 主队列
可以看到:
- 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(虽然
异步执行
具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中)。 - 所有任务是在打印的
syncConcurrent---begin
和syncConcurrent---end
之后才开始执行的(异步执行不会做任何等待,可以继续执行任务)。 - 任务是按顺序执行的(因为主队列是
串行队列
,每次只有一个任务被执行,任务一个接一个按顺序执行)。
这些麻烦的不同队列+不同任务
总算是搞懂啦,接着我们来了解一下 GCD
线程间的通信
GCD 线程间的通信
在 iOS
开发过程中,我们一般在主线程里边进行 UI
刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。
/**
* 线程间通信
*/
- (void)communication {
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 异步追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
// 回到主线程
dispatch_async(mainQueue, ^{
// 追加在主线程中执行的任务
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
});
}
打印结果如下:
可以看到在其他线程中先执行任务,执行完了之后回到主线程执行主线程的相应操作。
GCD 的其他方法
栅栏方法:dispatch_barrier_async
- 我们有时候需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作,当然操作组里也可以包含一个或者多个任务
- 这就需要用到
dispatch_barrier_async
方法在两个操作组间形成栅栏 dispatch_barrier_async
方法会等待前边追加到并发队列中的任务全部执行完毕后,再将制定的任务追加到该异步队列中。然后在dispatch_barrier_async
方法追加的任务执行完毕之后,接着追加任务到该异步队列并开始执行,大佬博客中的示意图非常的形象,具体图示如下:
栅栏方法的代码使用样例如下:
- (void) barrier {
dispatch_queue_t queue = dispatch_queue_create("net.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 4
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
});
}
运行结果如下:
在执行完栅栏前面的操作之后执行栅栏操作,最后再执行栅栏后面的操作
GCD中同步栅栏和异步栅栏
我们之前考虑异步栅栏+单一队列
的时候栅栏只作用于同一队列
那么对于身处不同队列的任务又有什么样的拦截作用呢?
对于重要的栅栏方法部分,我们将各种情况都实验一下:
异步栅栏+单一串行队列:
(由于异步执行+串行队列本身就是在创建的唯一一个新线程里按任务添加顺序排队执行,所以其实在这种情况下添加栅栏是没有意义的)
- (void) asyncBarrierAndOneSerial {
dispatch_queue_t queue = dispatch_queue_create("net.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 4
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
});
}
运行结果如下:
我们可以看到是非常规矩的按添加任务的顺序一个一个排队来执行
异步栅栏+单一并行队列:
(该种情况上方已经讲述过了)
- (void) asyncBarrierAndOneConcurrent {
dispatch_queue_t queue = dispatch_queue_create("net.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 4
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
});
}
运行结果:
可以看到栅栏的确是将前后添加的任务分割开了,先执行栅栏前的任务,再执行栅栏中的任务,最后再执行栅栏之后的任务,不过由于是在一个异步队列中,异步队列会开启新线程,所以我们在栅栏前的任务组中任务的执行结束的顺序和栅栏后的任务组中的任务执行结束的顺序是不确定的。
同步栅栏+单一串行队列:
- (void) syncBarrierAndOneSerial {
dispatch_queue_t queue = dispatch_queue_create("net.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_sync(queue, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 4
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
});
}
运行结果:
我们可以看到在串行队列中无论是同步执行还是异步执行,都是排好队一个一个按顺序来执行的。
同步栅栏+单一并行队列:
- (void) syncBarrierAndOneConcurrent {
dispatch_queue_t queue = dispatch_queue_create("net.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_sync(queue, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 4
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
});
}
运行结果如下:
实际的运行结果是栅栏前的任务组
(也就是任务1
和任务2
),在程序开始执行两秒之后同时打印了结果,接着两秒的时间单独执行了栅栏
中的方法,最后两秒时间同时执行了栅栏后的任务组
(也就是任务3
和任务4
),而且由于栅栏前后的任务组中的任务都是在并行队列中异步执行,所以执行结束的顺序是不确定的。
异步栅栏+多个串行队列:
- (void) asyncBarrierAndSerials {
dispatch_queue_t queueFirst = dispatch_queue_create("net.testQueueFirst", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueSecond = dispatch_queue_create("net.testQueueSecond", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueThird = dispatch_queue_create("net.testQueueThird", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueFourth = dispatch_queue_create("net.testQueueFourth", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueFifth = dispatch_queue_create("net.testQueueFifth", DISPATCH_QUEUE_SERIAL);
dispatch_async(queueFirst, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queueSecond, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_async(queueThird, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queueFourth, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queueFifth, ^{
// 追加任务 4
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
});
}
第一次运行结果如下:
第二次运行结果如下:
我们从上方两次的运行结果中可以看到,异步栅栏+多个串行队列
的情况下每个任务都是几乎同时执行的,五个任务执行的结束时间都是完全随机的,此时的栅栏也就失去了该有的意义。
异步栅栏+多个并行队列:
异步栅栏+多个串行队列
情况的各任务执行结束时间都是完全随机的,所以异步栅栏+多个并行队列
更是可想而知,肯定也是完全随机的。
- (void) asyncBarrierAndConcurrents {
dispatch_queue_t queueFirst = dispatch_queue_create("net.testQueueFirst", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queueSecond = dispatch_queue_create("net.testQueueSecond", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queueThird = dispatch_queue_create("net.testQueueThird", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queueFourth = dispatch_queue_create("net.testQueueFourth", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queueFifth = dispatch_queue_create("net.testQueueFifth", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queueFirst, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queueSecond, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_async(queueThird, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queueFourth, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queueFifth, ^{
// 追加任务 4
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
});
}
第一次运行结果:
第二次运行结果:
从上方两次的执行结果我们可以看出:各个任务的执行结束时间都是完全随机的,跟异步栅栏+多个串行队列
时的情况非常类似。
同步栅栏+多个串行队列:
- (void) syncBarrierAndSerials {
dispatch_queue_t queueFirst = dispatch_queue_create("net.testQueueFirst", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueSecond = dispatch_queue_create("net.testQueueSecond", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueThird = dispatch_queue_create("net.testQueueThird", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueFourth = dispatch_queue_create("net.testQueueFourth", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueFifth = dispatch_queue_create("net.testQueueFifth", DISPATCH_QUEUE_SERIAL);
dispatch_async(queueFirst, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queueSecond, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_sync(queueThird, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queueFourth, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queueFifth, ^{
// 追加任务 4
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
});
}
第一次运行结果:
第二次运行结果:
这种情况下的栅栏
和任务1
还有任务2
是几乎同时执行同时先出结果的(而且每次栅栏都是第一个出结果),但是由于同步的栅栏
占用了主线程
,就导致栅栏后的任务3
和任务4
只能等到栅栏中的任务执行完成之后再开始去执行。
同步栅栏+多个并行队列:
- (void) syncBarrierAndConcurrents {
dispatch_queue_t queueFirst = dispatch_queue_create("net.testQueueFirst", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queueSecond = dispatch_queue_create("net.testQueueSecond", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queueThird = dispatch_queue_create("net.testQueueThird", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queueFourth = dispatch_queue_create("net.testQueueFourth", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queueFifth = dispatch_queue_create("net.testQueueFifth", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queueFirst, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queueSecond, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_sync(queueThird, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queueFourth, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queueFifth, ^{
// 追加任务 4
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
});
}
第一次运行结果如下:
第二次运行结果如下:
实际运行过程中,任务1
、任务2
和栅栏
都是同时先开始去执行的,而且三者执行结束的时间是不确定的,然而由于栅栏
占用了主线程
的原因,任务3
和任务4
只有等到栅栏
执行完成之后才开始执行。
延时执行方法:dispatch_after
我们经常会遇到这样的需求:在指定时间(例如 3 秒
)之后执行某个任务。这种情况就可以用 GCD
的dispatch_after
方法来实现。
需要注意的是:dispatch_after
方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after
方法是很有效的。
/**
* 延时执行方法 dispatch_after
*/
- (void)after {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"asyncMain---begin");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0 秒后异步追加任务代码到主队列,并开始执行
NSLog(@"after---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"asyncMain---willEnd");
});
}
运行结果如下:
具体的运行情况是:先打印了asyncMain---begin
,接着过了两秒后紧接着按顺序打印了after---<_NSMainThread: 0x60000110c900>{number = 1, name = main}
和asyncMain---willEnd
GCD 一次性代码(只执行一次):dispatch_once
我们在创建单例
、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD
的 dispatch_once
方法。使用 dispatch_once
方法能保证某段代码在程序运行过程中只被执行 1 次
,并且即使在多线程的环境下,dispatch_once
也可以保证线程安全。
/**
* 一次性代码(只执行一次)dispatch_once
*/
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行 1 次的代码(这里面默认是线程安全的)
});
}
GCD 快速迭代方法:dispatch_apply
通常我们会用 for 循环
遍历,但是 GCD
给我们提供了快速迭代的方法dispatch_apply
。dispatch_apply
按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
如果是在串行队列中使用 dispatch_apply
,那么就和 for 循环
一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。
我们可以利用并发队列
进行异步执行
。比如说遍历 0~5
这 6
个数字,for
循环的做法是每次取出一个元素,逐个遍历。dispatch_apply
可以在多个线程中同时(异步)遍历多个数字。
还有一点,无论是在串行队列,还是并发队列中,dispatch_apply
都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait
方法
/**
* 快速迭代方法 dispatch_apply
*/
- (void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
}
运行结果如下:
因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序
也不定。但是 apply---end
一定在最后执行。这是因为 dispatch_apply
方法会等待全部任务执行完毕
GCD 队列组:dispatch_group
有时候我们会有这样的需求:分别异步执行2
个耗时任务,然后当2
个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD
的队列组。
- 调用队列组的
dispatch_group_async
先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的dispatch_group_enter
、dispatch_group_leave
组合来实现dispatch_group_async
- 调用队列组的
dispatch_group_notify
回到指定线程执行任务。或者使用dispatch_group_wait
回到当前线程继续向下执行(会阻塞当前线程)
dispatch_group_notify
监听 group
中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务:
- (void)group {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"blk0!");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk1!");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk3!");
});
//dispatch_group_notify会等到group中的处理全部结束时再开始执行
//在group中的处理全部结束时,将第三个参数(block)追加到第二个参数所对应的queue中
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"done");
});
}
运行结果如下:
我们可以看到,由于添加到group
中的队列在多线程并发时的执行结果时间是不确定的,所以打印的顺序都是随机的。
dispatch_group_wait
另外我们也可以使用dispatch_group_wait(group, DISPATCH_TIME_FOREVER);//第二个参数为dispatch_time_t类型,可以自定义
来等待group中的处理全部结束
dispatch_group_wait
用于暂停当前线程(阻塞当前线程),等待指定的 group
中的任务执行完成后,才会往下继续执行。
如果我们不去添加dispatch_group_wait
来进行等待,那么由于group
中的处理本身也是异步的,所以就会在group
中的处理还没有执行完时就去执行其他的任务,例子如下:
- (void)group {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"blk0!");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk1!");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk3!");
});
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// NSLog(@"done");
// });
NSLog(@"YES!!");
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//NSLog(@"YES!!");
}
打印结果如下:
可以看到打印YES!!
操作在group
中的处理还没有执行完时就已经执行了
而像下面这样:
- (void)group {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"blk0!");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk1!");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk3!");
});
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// NSLog(@"done");
// });
//NSLog(@"YES!!");
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"YES!!");
}
运行结果如下:
可以看到就是在group
中全部的处理执行完之后再执行的打印YES!!
操作
从 dispatch_group_wait
相关代码运行输出结果可以看出: 当所有任务执行完成之后,才执行 dispatch_group_wait
之后的操作。但是,使用dispatch_group_wait
会阻塞当前线程!
dispatch_group_enter、dispatch_group_leave
dispatch_group_enter
标志着一个任务追加到group
,执行一次,相当于group
中未执行完毕任务数+1
dispatch_group_leave
标志着一个任务离开了group
,执行一次,相当于group
中未执行完毕任务数-1
- 当
group
中未执行完毕任务数为0
的时候,才会使dispatch_group_wait
解除阻塞,以及执行追加到dispatch_group_notify
中的任务
接着我们来看一下通过 dispatch_group_enter
和 dispatch_group_leave
配和来实现向group
添加操作:
- (void)groupWithEnterAndLeave {
// 首先 需要创建一个线程组
dispatch_group_t group = dispatch_group_create();
// 任务1
dispatch_group_enter(group);
void (^blockFirst)(int) = ^(int a){
NSLog(@"任务%d完成!", a);
dispatch_group_leave(group);
};
blockFirst(1);
// 任务2
dispatch_group_enter(group);
void (^blockSecond)(int) = ^(int a){
NSLog(@"任务%d完成!", a);
dispatch_group_leave(group);
};
blockSecond(2);
// 全部完成
dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
NSLog(@"全部完成");
});
}
运行结果如下:
我们可以看到:任务1
和任务2
执行完成之后,才会执行全部完成
中的任务。
从 dispatch_group_enter
、dispatch_group_leave
相关代码运行结果中可以看出:当所有任务执行完成之后,才执行 dispatch_group_notify
中的任务。这里的dispatch_group_enter
、dispatch_group_leave
组合,其实等同于dispatch_group_async
。
不过使用dispatch_group_enter
和 dispatch_group_leave
需要成对出现:
如果 dispatch_group_leave
的调用次数多于 dispatch_group_enter
的调用次数,程序会 crash
,如下所示:
相反dispatch_group_enter
的调用次数多于dispatch_group_leave
,虽然不会发生 crash
, 但可能不会达到预期效果,如下所示:
上图中我们发现只打印了任务1完成
和任务2完成
,但是dispatch_group_notify
中需要执行的操作将会一直等待。
GCD 信号量:dispatch_semaphore
GCD
中的信号量是指 Dispatch Semaphore
,是持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore
中,使用计数来完成这个功能,计数小于 0
时需要等待,不可通过。计数为 0
或大于 0
时,不用等待可通过。计数大于 0 且计数减 1 时
不用等待,可通过。 Dispatch Semaphore
提供了三个方法:
dispatch_semaphore_create
:创建一个Semaphore
并初始化信号的总量dispatch_semaphore_signal
:发送一个信号,让信号总量加 1
dispatch_semaphore_wait
:可以使总信号量减 1
,信号总量小于 0
时就会一直等待(阻塞所在线程),否则就可以正常执行
注意: 信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。
Dispatch Semaphore
在实际开发中主要用于:
- 保持线程同步,将异步执行任务转换为同步执行任务
- 保证线程安全,为线程加锁
Dispatch Semaphore 线程同步
我们在开发中,会遇到这样的需求:异步执行耗时任务,并使用异步执行的结果进行一些额外的操作。换句话说,相当于,将将异步执行任务转换为同步执行任务。
下面我们就来利用Dispatch Semaphore
实现线程同步,将一步执行任务转换为同步执行任务:
/**
* semaphore 线程同步
*/
- (void)semaphoreSync {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"semaphore---begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int number = 0;
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
number = 100;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %zd",number);
}
运行结果如下:
可以看到semaphore---end
是在执行完 number = 100;
之后才打印的。而且输出结果 number
为 100
。整个的执行顺序如下:
semaphore
初始创建时计数为0
异步执行
将任务 1
追加到队列之后,不做等待,接着执行dispatch_semaphore_wait
方法,semaphore 减 1
,此时semaphore == -1
,当前线程进入等待状态(后面的内容不执行,只执行我们所添加的任务1
,等到dispatch_semaphore_signal
操作使信号量计数>=0
时线程才会恢复正常运作)- 然后,异步
任务 1
开始执行。任务 1
执行到dispatch_semaphore_signal
之后,总信号量加 1
,此时semaphore == 0
,正在被阻塞的线程(主线程)恢复继续执行 - 最后打印
semaphore---end,number = 100
这样就实现了线程同步,将异步执行任务转换为同步执行任务。
Dispatch Semaphore 线程安全和线程同步(为线程加锁)
线程安全: 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。
线程同步: 可理解为线程 A
和 线程 B
一块配合,线程 A
执行到一定程度时要依靠线程B
的某个结果,于是停下来,示意 线程B
运行;线程B
依言执行,再将结果给 线程A
;线程A
再继续操作。
举个简单例子就是:两个人在一起聊天。两个人不能同时说话,避免听不清(操作冲突)。等一个人说完(一个线程结束操作),另一个再说(另一个线程再开始操作)。
下面,我们模拟火车票售卖的方式,实现 NSThread
线程安全和解决线程同步问题(例子借鉴自:大佬博客)。
场景: 总共有 50
张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。
非线程安全(不使用 semaphore)
先来看看不考虑线程安全的代码:
@interface ViewController ()
@property (nonatomic, assign) NSInteger ticketSurplusCount;
@end
/**
* 非线程安全:不使用 semaphore
* 初始化火车票数量、卖票窗口(非线程安全)、并开始卖票
*/
- (void)initTicketStatusNotSafe {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"semaphore---begin");
self.ticketSurplusCount = 50;
// queue1 代表北京火车票售卖窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上海火车票售卖窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketNotSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketNotSafe];
});
}
/**
* 售卖火车票(非线程安全)
*/
- (void)saleTicketNotSafe {
while (1) {
if (self.ticketSurplusCount > 0) { // 如果还有票,继续售卖
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else { // 如果已卖完,关闭售票窗口
NSLog(@"所有火车票均已售完");
break;
}
}
}
运行结果如下:
可以看到在不考虑线程安全,不使用 semaphore
的情况下,得到票数是错乱的,而且同一张票可能会发生卖两遍的情况,这样显然不符合我们的需求,所以我们需要考虑线程安全问题
线程安全(使用 semaphore 加锁)
考虑线程安全的代码:
@interface ViewController ()
@property (nonatomic, assign) NSInteger ticketSurplusCount;
@end
//创建一个全局信号量
dispatch_semaphore_t semaphoreLock;
/**
* 线程安全:使用 semaphore 加锁
* 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
*/
- (void)initTicketStatusSafe {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"semaphore---begin");
semaphoreLock = dispatch_semaphore_create(1);
self.ticketSurplusCount = 50;
// queue1 代表北京火车票售卖窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上海火车票售卖窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketSafe];
});
}
/**
* 售卖火车票(线程安全)
*/
- (void)saleTicketSafe {
while (1) {
// 相当于加锁
dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
if (self.ticketSurplusCount > 0) { // 如果还有票,继续售卖
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else { // 如果已卖完,关闭售票窗口
NSLog(@"所有火车票均已售完");
// 相当于解锁
dispatch_semaphore_signal(semaphoreLock);
break;
}
// 相当于解锁
dispatch_semaphore_signal(semaphoreLock);
}
}
运行结果如下:
思路: 此处我们采用了dispatch_semaphore
机制,买票的操作是每次异步执行的,但是如果第一张票还没卖出去第二张票已经开始卖了的话就会由于dispatch_semaphore_wait
操作使得信号量计数=-1
,线程就会进入等待状态,等待第一张票卖完之后的dispatch_semaphore_signal
操作,这个操作会让信号量的计数=1
,使得线程重写开始正常运行,开始正常执行卖第二张票的处理,以此类推,通过保护每一次的卖票从而实现整个售票流程的正确性。
可以看出,在考虑了线程安全的情况下,使用 dispatch_semaphore
机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。
GCD一些题目
题目一:
以下代码输出结果是什么?
-(void)interviewFirst {
dispatch_queue_t queue = dispatch_queue_create("com.demo.queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"test____1"); // 任务1
dispatch_async(queue, ^{
NSLog(@"test____2"); // 任务2
dispatch_sync(queue, ^{
NSLog(@"test____3"); // 任务3
});
NSLog(@"test____4"); // 任务4
});
NSLog(@"test____5"); // 任务5
}
运行结果:
我们可以看到输出的结果为:1、5、2、3、4
。可以说这个就是答案,但是又不完全对。
首先这段代码是在主线程中执行的,那么在主线程执行的内容我们可以分为3个
部分:
任务1
肯定是最先执行的,然后去执行第2块内容
,最后执行任务5
,再继续看第2块部分
使用异步函数配合并发队列,那么disptch
里面的block
将会在一个新的线程执行。同样的在这个线程里面也可以将执行的代码分成3
个部分:
在这块代码里面任务2
会被先执行,然后再看dispatch_syns
,同步函数
,那么都不需要看是什么对列,任务3
将会次之执行,最后就是执行任务4
剩下的就是需要考虑任务5
和任务2、3、4
的执行顺序了。正确答案其实是他们之间没有顺序,他们在不同的线程执行,理论上讲是可以同时执行的。而之所以打印出来任务2、3、4
在任务5
之后,其实是在执行任务2、3、4
之前,需要创建其执行的线程,创建线程需要消耗微小的时间,还有一方面原因是这块代码在主线程运行的,任务5
在主线程执行,效率也会更高。
这道题考查的是GCD
中同步、异步函数和串行、并行队列的使用,使用NSLog
来模拟任务执行,但正真的环境中任务的耗时程度是不一样的。
我们假设任务5
是一个耗时20微秒
的任务,那么打印结果会不会不一样呢?
-(void)interviewFirst {
dispatch_queue_t queue = dispatch_queue_create("com.demo.queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"test____1"); // 任务1
dispatch_async(queue, ^{
NSLog(@"test____2"); // 任务2
dispatch_sync(queue, ^{
NSLog(@"test____3"); // 任务3
});
NSLog(@"test____4"); // 任务4
});
//执行任务5前先等20微秒
usleep(120);
NSLog(@"test____5"); // 任务5
}
在任务5
之前usleep 120微秒
模拟耗时任务。
注意: usleep
和sleep
的区别只是单位不一样,usleep
的单位是微秒,sleep
的单位是秒。
现在的运行结果如下:
可以看到这时候顺序变成了 1、2、5、3、4
,正如我们分析结果,5
和 2、3、4
是没有顺序的。如果200微妙
,运行结果还会是不一样的。
现在将usleep
中的等待时间换成200微秒
,运行结果如下:
可以看到 5
居然走到了所有的 2、3、4
之后。
所以这道题面试题的正确答案完整版是: 先执行1
,5
在1
后执行,2、3、4
顺序执行,5
和2、3、4
是没有顺序。
题目二:
以下代码输出结果是什么?
-(void)interviewSecond {
dispatch_queue_t queue = dispatch_queue_create("com.demo.queue", DISPATCH_QUEUE_SERIAL);
NSLog(@"test____1"); // 任务1
dispatch_async(queue, ^{
NSLog(@"test____2"); // 任务2
dispatch_sync(queue, ^{
NSLog(@"test____3"); // 任务3
});
NSLog(@"test____4"); // 任务4
});
NSLog(@"test____5"); // 任务5
}
这道题的答案应该比较显而易见
运行结果就是:
可以看到是崩溃了,原因也非常简单:使用同步函数
在当前
的串行队列
中添加任务,会产生死锁。注意三个关键字,GCD发生死锁的充分条件。
同步异步和串行并发队列的搭配:
同步异步和串行并发队列的组合情况如下:
串行队列 | 并发队列 | |
---|---|---|
同步函数 | 可能死锁 | 不开启新线程 |
异步函数 | 开启新线程 | 开启新线程 |
在组合的时候,还需要考虑的因素还有当前代码的执行是在哪个队列之中,之前也有讲到:使用同步函数
在当前
串行队列中
添加任务,会产生死锁,如果在别的串行队列去添加任务是不会产生死锁的。例如:
//在主线程中执行的
- (void)interviewSecond {
dispatch_queue_t queue = dispatch_queue_create("com.demo.serial", DISPATCH_QUEUE_SERIAL);
NSLog(@"---任务1");
dispatch_sync(queue, ^{
NSLog(@"---任务2");
});
NSLog(@"---任务3");
}
这段代码就是同步函数
和串行队列
的时候,但不会造成死锁(在主线程中执行)
第三题:
下列代码的打印结果是什么?
//经典面试题
- (void)interviewThird {
//全局队列
dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);
__block int a = 0;
while (a < 100) {
dispatch_async(globQueue, ^{
// NSLog(@"内部: %d - %@",a,[NSThread currentThread]);
a++;
});
};
NSLog(@"外部打印_____ %d",a);
}
首先结果只有3个
,等于100
,大于100
,大于等于100
。等于100的情况肯定是有可能发生的。所以就看有没有可能发生大于100
的情况。代码走出while循环
的时候a=100
,就看在NSLog
打印的时候还回不回异步执行a++
了。再看a++
的操作是在其他线程异步完成的(异步函数+全局队列),也就是说,有可能来到了下一次循环的while判断
,上一次循环的a++
还没有执行。所以a++
执行的次数应该是 大于等于100
的。
实际第一次运行结果如下:
实际第二次运行结果如下:
可以看到实际的执行结果就是 a大于等于100
题目四:
这道题是第三题的变种,问这段代码打印结果是多少?
- (void)interviewFourth {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
__block int a = 0;
for (int i = 0; i < 100; i++) {
dispatch_async(globalQueue, ^{
a++;
});
}
NSLog(@"外部答应_____ %d",a);
}
这题是a++
只有100
次,就看在NSLog
执行的时候这100
个a++
是否都执行完了。同样是异步执行
,极限情况下是有可能执行完成的,这样打印的就刚好是100
,如果有没有执行完的a++
,那么打印出来的值有可能小于100
,所以极限最小打印值就是0
。
实际第一次运行结果如下:
也许是由于我们的NSLog操作在主线程,效率比全局的并行队列要高不少,所以反复执行的结果都是0,于是我们就在NSLog前面添加线程休眠时长,给我们的并行执行的a++操作争取时间:
- (void)interviewFourth {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
__block int a = 0;
for (int i = 0; i < 100; i++) {
dispatch_async(globalQueue, ^{
a++;
});
}
usleep(500);
NSLog(@"外部答应_____ %d",a);
}
实际第二次运行结果如下:
可以看到还是没有达到最大极限值100
,所以我们再加大休眠时长为1000微秒
:
- (void)interviewFourth {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
__block int a = 0;
for (int i = 0; i < 100; i++) {
dispatch_async(globalQueue, ^{
a++;
});
}
usleep(1000);
NSLog(@"外部答应_____ %d",a);
}
实际第三次运行结果如下:
可以看到,三次的运行结果都非常符合我们的预期结果。
下面我们来讲解一下GCD
部分内容的源码实现相关内容:
GCD部分源码
串行队列和并发队列的源码解析
在我们开发中,使用队列的时候,苹果给我们给了3
个获取队列的api
- (void)test01 {
//主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//全局并发队列
dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);
//自己创建的串行队列
dispatch_queue_t normalQueue = dispatch_queue_create("com.demo.serial", DISPATCH_QUEUE_SERIAL);
NSLog(@"%@",mainQueue);
NSLog(@"%@",globQueue);
NSLog(@"%@",normalQueue);
}
我们可以打开dispatch源码找到dispatch_queue_create
的api
实现部分,来一探究竟:
dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
return _dispatch_lane_create_with_target(label, attr,
DISPATCH_TARGET_QUEUE_DEFAULT, true);
}
可以看到是调用_dispatch_lane_create_with_target
并添加2个
默认参数实现的,找到对应实现:
DISPATCH_NOINLINE
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq, bool legacy)
{
//把我们传入的DISPATCH_QUEUE_SERIAL或者DISPATCH_QUEUE_CONCURRENT参数进行封装,封装成了dqai
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
//
// Step 1: Normalize arguments (qos, overcommit, tq)
//
dispatch_qos_t qos = dqai.dqai_qos;
#if !HAVE_PTHREAD_WORKQUEUE_QOS
if (qos == DISPATCH_QOS_USER_INTERACTIVE) {
dqai.dqai_qos = qos = DISPATCH_QOS_USER_INITIATED;
}
if (qos == DISPATCH_QOS_MAINTENANCE) {
dqai.dqai_qos = qos = DISPATCH_QOS_BACKGROUND;
}
#endif // !HAVE_PTHREAD_WORKQUEUE_QOS
后续代码省略...
可以看到这个方法里面,把我们传入的DISPATCH_QUEUE_SERIAL
或者DISPATCH_QUEUE_CONCURRENT
参数进行封装,封装成了dqai
。我们可以大致看看封装的实现:
dispatch_queue_attr_info_t
_dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
{
dispatch_queue_attr_info_t dqai = { };
//如果为空,直接返回
if (!dqa) return dqai;
#if DISPATCH_VARIANT_STATIC
if (dqa == &_dispatch_queue_attr_concurrent) {
//dqai里面有个dqai_concurrent的属性,代表是否是并发
dqai.dqai_concurrent = true;
return dqai;
}
#endif
if (dqa < _dispatch_queue_attrs ||
dqa >= &_dispatch_queue_attrs[DISPATCH_QUEUE_ATTR_COUNT]) {
#ifndef __APPLE__
if (memcmp(dqa, &_dispatch_queue_attrs[0],
sizeof(struct dispatch_queue_attr_s)) == 0) {
dqa = (dispatch_queue_attr_t)&_dispatch_queue_attrs[0];
} else
#endif // __APPLE__
DISPATCH_CLIENT_CRASH(dqa->do_vtable, "Invalid queue attribute");
}
size_t idx = (size_t)(dqa - _dispatch_queue_attrs);
dqai.dqai_inactive = (idx % DISPATCH_QUEUE_ATTR_INACTIVE_COUNT);
idx /= DISPATCH_QUEUE_ATTR_INACTIVE_COUNT;
//又调用了dqai_concurrent属性
dqai.dqai_concurrent = !(idx % DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT);
idx /= DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT;
dqai.dqai_relpri = -(int)(idx % DISPATCH_QUEUE_ATTR_PRIO_COUNT);
idx /= DISPATCH_QUEUE_ATTR_PRIO_COUNT;
dqai.dqai_qos = idx % DISPATCH_QUEUE_ATTR_QOS_COUNT;
idx /= DISPATCH_QUEUE_ATTR_QOS_COUNT;
dqai.dqai_autorelease_frequency =
idx % DISPATCH_QUEUE_ATTR_AUTORELEASE_FREQUENCY_COUNT;
idx /= DISPATCH_QUEUE_ATTR_AUTORELEASE_FREQUENCY_COUNT;
dqai.dqai_overcommit = idx % DISPATCH_QUEUE_ATTR_OVERCOMMIT_COUNT;
idx /= DISPATCH_QUEUE_ATTR_OVERCOMMIT_COUNT;
return dqai;
}
从上方我们可以获取到2个
有用的点,dqai
里面有个dqai_concurrent
的属性,顾名思义是代表是否是并发,那么默认的就是串行。
我们在继续看如何根据dqai
创建队列的(在_dispatch_lane_create_with_target
方法中):
上方代码省略...
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s));
//下面这个初始化方法的第三个参数,如果是并发就传入DISPATCH_QUEUE_WIDTH_MAX,如果是串行就传入1
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));
dq->dq_label = label;
dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
dqai.dqai_relpri);
if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
}
if (!dqai.dqai_inactive) {
_dispatch_queue_priority_inherit_from_target(dq, tq);
_dispatch_lane_inherit_wlh_from_target(dq, tq);
}
_dispatch_retain(tq);
dq->do_targetq = tq;
_dispatch_object_debug(dq, "%s", __func__);
return _dispatch_trace_queue_create(dq)._dq;
}
接着我们来看一下DISPATCH_QUEUE_WIDTH_MAX
的宏定义:
#define DISPATCH_QUEUE_WIDTH_FULL_BIT 0x0020000000000000ull
#define DISPATCH_QUEUE_WIDTH_FULL 0x1000ull
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
//利用第二行的宏定义的值进行计算得到DISPATCH_QUEUE_WIDTH_MAX
#define DISPATCH_QUEUE_WIDTH_MAX (DISPATCH_QUEUE_WIDTH_FULL - 2)
#define DISPATCH_QUEUE_USES_REDIRECTION(width) \
({ uint16_t _width = (width); \
_width > 1 && _width < DISPATCH_QUEUE_WIDTH_POOL; })
可以计算DISPATCH_QUEUE_WIDTH_MAX
的结果是14
我们再看_dispatch_queue_init
函数的内部实现:
//该方法里面主要注意一下width相关的内容
static inline dispatch_queue_class_t
_dispatch_queue_init(dispatch_queue_class_t dqu, dispatch_queue_flags_t dqf,
uint16_t width, uint64_t initial_state_bits)
{
uint64_t dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(width);
//让dq指向dqu的_dq属性,然后用dq对_dq进行操作和设定,最后返回dqu
dispatch_queue_t dq = dqu._dq;
dispatch_assert((initial_state_bits & ~(DISPATCH_QUEUE_ROLE_MASK |
DISPATCH_QUEUE_INACTIVE)) == 0);
if (initial_state_bits & DISPATCH_QUEUE_INACTIVE) {
dq->do_ref_cnt += 2; // rdar://8181908 see _dispatch_lane_resume
if (dx_metatype(dq) == _DISPATCH_SOURCE_TYPE) {
dq->do_ref_cnt++; // released when DSF_DELETED is set
}
}
dq_state |= initial_state_bits;
dq->do_next = DISPATCH_OBJECT_LISTLESS;
//可以看到我们的width最后变成了DQF_WIDTH(width)
dqf |= DQF_WIDTH(width);
os_atomic_store2o(dq, dq_atomic_flags, dqf, relaxed);
dq->dq_state = dq_state;
dq->dq_serialnum =
os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);
return dqu;
}
可以看到如果width
最后变成了DQF_WIDTH(width)
接下来我们看看主队列的实现:
DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_CONST DISPATCH_NOTHROW
dispatch_queue_main_t
dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}
可以看到需要用到一个宏定义的函数,并且传入了2个
参数,其中_dispatch_main_q
是全局变量,定义如下:
struct dispatch_queue_static_s _dispatch_main_q = {
DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
#if !DISPATCH_USE_RESOLVERS
.do_targetq = _dispatch_get_default_queue(true),
#endif
.dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
DISPATCH_QUEUE_ROLE_BASE_ANON,
.dq_label = "com.apple.main-thread",
.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
.dq_serialnum = 1,
};
这里我们再次看到了DQF_WIDTH(1)
根据源码里面的信息,我们可以得知,串行队列和并发队列最根本的区别就是DQF_WIDTH
不同,串行队列的为1
。这个width
可以抽象的理解为队列出口的宽度。可以把串行队列想成一个单向单车道,把任务想成一辆辆车子,车子通过的时候必须一辆一辆按顺序通过;而并发队列可以想成单向多车道,有多个出口,车子可以并行通过。
当然也可以通过下图理解: