iOS和OS X的核心是XNU内核,GCD是基于XNU内核实现的。
GCD的API都在libdispatch库中。
GCD的底层实现主要有Dispatch Queue和Dispatch Source,Dispatch Queue管理block操作,Dispatch Source处理事件。
一、基础使用
GCD是基于C语言的一套多线程开发机制。
GCD有四个概念:串行队列、并行队列、同步、异步。
队列:装在线程里的队形结构(系统以先进先出的方式调度队列中的任务执行)。
任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。
1.同步sync
完成需要做的任务后才会返回,进行下一个任务。
同步方法功能类似dispatch_group_wait
, group 指的是所有线程,包括主线程。
不一定是多线程。
2.异步async
不会等待任务完成才返回,会立即返回。
异步是多线程的代名词,因为必定会开启新的线程,线程的申请是由异步负责,起到开分支的作用。
3.串行队列
任务依次执行,同一时间队列中只有一个任务在执行,每个任务只有在前一个任务执行完成后才能开始执行。不知道在一个block(任务)执行结束到下一个block(任务)开始执行之间的这段时间时间是多长。
4.并行队列
任务并发执行。唯一能保证的是,这些任务会按照被添加的顺序开始执行。但是任务可以以任何顺序完成 。 不知道在执行下一个任务是从什么时候开始,或者说任意时刻有多个block(任务)运行,这个完全是取决于GCD。
5.全局队列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
隶属于并行队列。
不要与barrier栅栏方法搭配使用,barrier 只有与自定义的并行队列一起使用,才能让 barrier 达到我们所期望的栅栏功能。与 串行队列或者global 队列 一起使用,barrier 的表现会和dispatch_sync
方法一样。
6.主队列
dispatch_get_main_queue()
隶属于串行队列。
不能与sync同步方法搭配使用,会造成死循环。
7.创建队列
/*
dispatch_queue_create创建队列
第一个参数为队列的唯一标识符,可为空
第二个参数用来表示串行队列DISPATCH_QUEUE_SERIAL、并行队列DISPATCH_QUEUE_CONCURRENT
*/
dispatch_queue_t queueSerial = dispatch_queue_create("queueSerial", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueConcurrent = dispatch_queue_create("queueConcurrent", DISPATCH_QUEUE_CONCURRENT);
8.同步串行队列
执行完一个任务再执行下一个任务,不开启新线程。
dispatch_queue_t queueSerial = dispatch_queue_create("queueSerial", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queueSerial, ^{
NSLog(@"1= %@",[NSThread currentThread]);
});
NSLog(@"2= %@",[NSThread currentThread]);
dispatch_sync(queueSerial, ^{
NSLog(@"3= %@",[NSThread currentThread]);
});
NSLog(@"4= %@",[NSThread currentThread]);
//输出结果
2021-05-20 14:51:23.906225+0800 DJGCDDemo[24608:231951] 1= {number = 1, name = main}
2021-05-20 14:51:23.906364+0800 DJGCDDemo[24608:231951] 2= {number = 1, name = main}
2021-05-20 14:51:23.906471+0800 DJGCDDemo[24608:231951] 3= {number = 1, name = main}
2021-05-20 14:51:23.906563+0800 DJGCDDemo[24608:231951] 4= {number = 1, name = main}
9.异步串行
开启一条新线程,但任务是串行的,还是按顺序执行(但不能保证任务什么时候执行完成)。
dispatch_queue_t queueSerial = dispatch_queue_create("queueSerial", DISPATCH_QUEUE_SERIAL);
dispatch_async(queueSerial, ^{
NSLog(@"1= %@",[NSThread currentThread]);
});
NSLog(@"2= %@",[NSThread currentThread]);
dispatch_async(queueSerial, ^{
NSLog(@"3= %@",[NSThread currentThread]);
});
NSLog(@"4= %@",[NSThread currentThread]);
//输出结果
2021-05-20 14:53:44.567875+0800 DJGCDDemo[24628:233496] 2= {number = 1, name = main}
2021-05-20 14:53:44.567884+0800 DJGCDDemo[24628:233547] 1= {number = 6, name = (null)}
2021-05-20 14:53:44.568024+0800 DJGCDDemo[24628:233496] 4= {number = 1, name = main}
2021-05-20 14:53:44.568024+0800 DJGCDDemo[24628:233547] 3= {number = 6, name = (null)}
10.串行队列中同步异步的区别
串行队列能确保顺序执行任务,他们两个的唯一区别在于dispatch_sync
只会在block完全执行完之后返回,dispatch_async
不能确保会在block完全执行完之后返回,唯一能确定的是会在被添加到queue队列后返回。
11.同步并行队列
因为是同步的,执行完一个任务,在执行下一个任务,不开启新线程。
dispatch_queue_t queueConcurrent = dispatch_queue_create("queueSerial", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queueConcurrent, ^{
NSLog(@"1= %@",[NSThread currentThread]);
});
NSLog(@"2= %@",[NSThread currentThread]);
dispatch_sync(queueConcurrent, ^{
NSLog(@"3= %@",[NSThread currentThread]);
});
NSLog(@"4= %@",[NSThread currentThread]);
//输出结果
2021-05-20 14:57:38.354956+0800 DJGCDDemo[24690:236419] 1= {number = 1, name = main}
2021-05-20 14:57:38.355106+0800 DJGCDDemo[24690:236419] 2= {number = 1, name = main}
2021-05-20 14:57:38.355205+0800 DJGCDDemo[24690:236419] 3= {number = 1, name = main}
2021-05-20 14:57:38.355306+0800 DJGCDDemo[24690:236419] 4= {number = 1, name = main}
12.异步并行队列
任务交替执行,开启多个线程。
dispatch_queue_t queueConcurrent = dispatch_queue_create("queueSerial", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queueConcurrent, ^{
NSLog(@"1= %@",[NSThread currentThread]);
});
NSLog(@"2= %@",[NSThread currentThread]);
dispatch_async(queueConcurrent, ^{
NSLog(@"3= %@",[NSThread currentThread]);
});
NSLog(@"4= %@",[NSThread currentThread]);
//输出结果
2021-05-20 14:59:13.180098+0800 DJGCDDemo[24709:237691] 2= {number = 1, name = main}
2021-05-20 14:59:13.180097+0800 DJGCDDemo[24709:237742] 1= {number = 7, name = (null)}
2021-05-20 14:59:13.180240+0800 DJGCDDemo[24709:237745] 3= {number = 6, name = (null)}
2021-05-20 14:59:13.180238+0800 DJGCDDemo[24709:237691] 4= {number = 1, name = main}
13.总结
队列 | 同步 | 异步 |
---|---|---|
串行队列 | 不会新建线程,依然在当前线程上。类似同步锁,是同步锁的替代方案。常用 |
会新建线程,只开一条线程。每次使用createDispatch方法就会新建一条线程,多次调用该方法,会创建多条线程,多条线程间会并行执行。 |
并行队列 | 不会新建线程,依然在当前线程上。 | 会新建线程,可以开多条线程。iOS7-SDK 时代一般是5、6条, iOS8-SDK 以后可以50、60条 。常用 |
经典死锁,运行会崩溃Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor orangeColor];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"11");
});
NSLog(@"22");
/*
放入dispatch_main_queue中的任务会被放到主线程执行,
同步方法sync是将任务放入队列,然后等待任务完成后才会返回,
主队列当前执行的为viewDidLoad方法,由此就又成了一个互相等待的死锁,
即viewDidLoad方法须等待dispatch_sync这个同步方法执行完后继续执行,
而同步方法又在等待队列中排在他前面的任务viewDidLoad执行完成..waiting…
*/
}
14.线程间通信
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 异步追加任务
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);
}
// 回到主线程
dispatch_async(mainQueue, ^{
// 追加在主线程中执行的任务
[NSThread sleepForTimeInterval:2];
NSLog(@"2---%@",[NSThread currentThread]);
});
});
//输出结果
2021-05-20 15:05:13.790142+0800 DJGCDDemo[24737:241173] 1---{number = 6, name = (null)}
2021-05-20 15:05:15.791595+0800 DJGCDDemo[24737:241173] 1---{number = 6, name = (null)}
2021-05-20 15:05:17.792681+0800 DJGCDDemo[24737:241083] 2---{number = 1, name = main}
二、高级使用
1.栅栏方法:dispatch_barrier_(a)sync
有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async
方法在两个操作组间形成栅栏。dispatch_barrier_async
函数会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在dispatch_barrier_async
函数追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。
(1)dispatch_barrier_async
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"1 = %@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"2 = %@",[NSThread currentThread]);
});
NSLog(@"3");
dispatch_barrier_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"----barrier---%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"4 = %@",[NSThread currentThread]);
});
NSLog(@"5");
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"6 = %@",[NSThread currentThread]);
});
//输出结果
2021-05-20 21:37:01.368151+0800 DJGCDDemo[1316:34848] 3
2021-05-20 21:37:01.368293+0800 DJGCDDemo[1316:34848] 5
2021-05-20 21:37:03.372472+0800 DJGCDDemo[1316:35018] 1 = {number = 7, name = (null)}
2021-05-20 21:37:03.372482+0800 DJGCDDemo[1316:35017] 2 = {number = 6, name = (null)}
2021-05-20 21:37:05.374992+0800 DJGCDDemo[1316:35017] ----barrier---{number = 6, name = (null)}
2021-05-20 21:37:07.375537+0800 DJGCDDemo[1316:35018] 6 = {number = 7, name = (null)}
2021-05-20 21:37:07.375534+0800 DJGCDDemo[1316:35017] 4 = {number = 6, name = (null)}
(2)dispatch_barrier_sync
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
//任务1
[NSThread sleepForTimeInterval:2];
NSLog(@"1 = %@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
//任务2
[NSThread sleepForTimeInterval:2];
NSLog(@"2 = %@",[NSThread currentThread]);
});
NSLog(@"3");
dispatch_barrier_sync(concurrentQueue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"----barrier---%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
//任务3
[NSThread sleepForTimeInterval:2];
NSLog(@"4 = %@",[NSThread currentThread]);
});
NSLog(@"5");
dispatch_async(concurrentQueue, ^{
//任务4
[NSThread sleepForTimeInterval:2];
NSLog(@"6 = %@",[NSThread currentThread]);
});
//输出结果
2021-05-20 21:39:52.505630+0800 DJGCDDemo[1339:36955] 3
2021-05-20 21:39:54.507482+0800 DJGCDDemo[1339:37160] 1 = {number = 5, name = (null)}
2021-05-20 21:39:54.507479+0800 DJGCDDemo[1339:37158] 2 = {number = 6, name = (null)}
2021-05-20 21:39:56.507953+0800 DJGCDDemo[1339:36955] ----barrier---{number = 1, name = main}
2021-05-20 21:39:56.508338+0800 DJGCDDemo[1339:36955] 5
2021-05-20 21:39:58.512306+0800 DJGCDDemo[1339:37158] 4 = {number = 6, name = (null)}
2021-05-20 21:39:58.512306+0800 DJGCDDemo[1339:37159] 6 = {number = 8, name = (null)}
(3)总结
- 在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。
- 需要注意的是,所有任务的执行必须保证在同一个队列里,栅栏函数才有效
-
dispatch_barrier_sync
和dispatch_barrier_async
共同点:都会等待在它前面插入的任务执行完,在执行自己的任务,最后执行在它之后的任务。 -
dispatch_barrier_sync
和dispatch_barrier_async
不同点:dispatch_barrier_sync
会等待它的任务执行完成之后,插入它后面的任务到队列。dispatch_barrier_async
将自己的任务插入到队列后,不会等待自己的任务执行完成,会继续插入后面的任务到队列。
2.GCD 延时执行方法:dispatch_after
在指定时间之后执行某个任务。可以用 GCD 的dispatch_after
函数来实现。
需要注意的是:dispatch_after
函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after
函数是很有效的。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5.0), dispatch_get_global_queue(0, 0), ^{
NSLog(@"1 = %@",[NSThread currentThread]);
});
3.只执行一次:dispatch_once
我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了GCD的dispatch_once
函数。使用dispatch_once
函数能保证某段代码在程序运行过程中只被执行1次,并且即时在多线程的环境下,dispatch_once
也可以保证线程安全。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
单例
+ (instancetype)shareInstance{
static Model * instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (instance == nil) {
instance = [[self alloc] init];
}
});
return instance;
}
4.快速迭代方法:dispatch_apply
通常我们会用for循环遍历,但是GCD给我们提供了快速迭代的函数dispatch_apply
。dispatch_apply
按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
如果是在串行队列中使用dispatch_apply
,那么就和for
循环一样,按顺序同步执行。可这样就体现不出快速迭代的意义了。
我们可以利用并发队列进行异步执行。比如说遍历 0~5 这6个数字,for
循环的做法是每次取出一个元素,逐个遍历。dispatch_apply
可以 在多个线程中同时(异步)遍历多个数字。
还有一点,无论是在串行队列,还是异步队列中,dispatch_apply
都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的dispatch_group_wait
方法。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"---begin---");
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"---end---");
//输出结果
2021-05-21 14:14:34.921661+0800 DJGCDDemo[34407:150346] ---begin---
2021-05-21 14:14:34.921865+0800 DJGCDDemo[34407:150346] 0---{number = 1, name = main}
2021-05-21 14:14:34.921992+0800 DJGCDDemo[34407:150346] 1---{number = 1, name = main}
2021-05-21 14:14:34.922121+0800 DJGCDDemo[34407:150346] 2---{number = 1, name = main}
2021-05-21 14:14:34.922168+0800 DJGCDDemo[34407:150498] 3---{number = 7, name = (null)}
2021-05-21 14:14:34.922254+0800 DJGCDDemo[34407:150498] 4---{number = 7, name = (null)}
2021-05-21 14:14:34.922293+0800 DJGCDDemo[34407:150500] 5---{number = 6, name = (null)}
2021-05-21 14:14:34.922376+0800 DJGCDDemo[34407:150346] ---end---
因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是end一定在最后执行。这是因为dispatch_apply
函数会等待全部任务执行完毕。
5.队列组:dispatch_group
有时候我们会有这样的需求:分别异步执行几个耗时任务,然后当耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。
调用队列组的dispatch_group_async
先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的dispatch_group_enter
、dispatch_group_leave
组合来实现dispatch_group_async
。
用队列组的dispatch_group_notify
回到指定线程执行任务。或者使用dispatch_group_wait
回到当前线程继续向下执行(会阻塞当前线程)。
(1)dispatch_group_notify
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"1 = %@",[NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"2 = %@",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"3 = %@",[NSThread currentThread]);
});
//输出结果
2021-05-21 14:44:46.444487+0800 DJGCDDemo[34623:164610] 2 = {number = 6, name = (null)}
2021-05-21 14:44:46.444537+0800 DJGCDDemo[34623:164609] 1 = {number = 5, name = (null)}
2021-05-21 14:44:46.450750+0800 DJGCDDemo[34623:164527] 3 = {number = 1, name = main}
(2)dispatch_group_wait
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"1 = %@",[NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"2 = %@",[NSThread currentThread]);
});
/*
DISPATCH_TIME_FOREVER 会等待队列组中的任务都执行完,在执行后面代码,是阻塞线程
DISPATCH_TIME_NOW 不会等待队列组任务都执行完,直接执行后面代码
*/
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"3 = %@",[NSThread currentThread]);
(3)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
组合,其实等同于dispatch_group_async
。
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"1 = %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"2 = %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"3 = %@",[NSThread currentThread]);
//输出结果
2021-05-21 15:18:11.001666+0800 DJGCDDemo[50079:203205] 2 = {number = 5, name = (null)}
2021-05-21 15:18:11.001666+0800 DJGCDDemo[50079:203204] 1 = {number = 7, name = (null)}
2021-05-21 15:18:11.001817+0800 DJGCDDemo[50079:203083] 3 = {number = 1, name = main}
6.dispatch_barrier
和dispatch_group
区别
dispatch_barrier
和dispatch_group
都可以阻塞一段任务执行的作用,区别在于利用dispatch_barrier
阻塞任务,所有任务的执行必须在同一个队列,而dispatch_group
各个任务的执行可以在不同的队列中。
7.信号量:dispatch_semaphore
在Dispatch Semaphore中,使用计数来完成这个功能,计数为0时等待,不可通过。计数为1或大于1时,计数减1且不等待,可通过。
dispatch_semaphore_create
:创建一个Semaphore并初始化信号的总量
dispatch_semaphore_signal
:发送一个信号,让信号加1
dispatch_semaphore_wait
:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。
Dispatch Semaphore开发中的主要作用:
- 保持线程同步,将异步执行任务转换为同步执行任务
- 保证线程安全,为线程加锁
- 控制多线程并发数量
(1)Dispatch Semaphore线程同步
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int number = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"1 = %@",[NSThread currentThread]);
number = 100;
dispatch_semaphore_signal(semaphore);//信号量加1
});
//等待异步执行完成后,信号量加1,信号量不是0时,继续执行
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"number = %d",number);
//输出结果
2021-05-25 14:12:34.379839+0800 DJGCDDemo[9589:108196] 1 = {number = 7, name = (null)}
2021-05-25 14:12:34.379973+0800 DJGCDDemo[9589:108130] number = 100
(2)Dispatch Semaphore线程安全和线程同步(为线程加锁)
@interface ViewController ()
@property(nonatomic , assign)int number;
@property(nonatomic , strong)dispatch_semaphore_t semaphore;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor orangeColor];
self.number = 2;
self.semaphore = dispatch_semaphore_create(1);
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[weakSelf numberChange];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[weakSelf numberChange];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[weakSelf numberChange];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[weakSelf numberChange];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[weakSelf numberChange];
});
}
-(void)numberChange{
__weak typeof(self) weakSelf = self;
//修改number,线程安全
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
if (weakSelf.number > 0) {
weakSelf.number -- ;
NSLog(@"%d = %@",weakSelf.number,[NSThread currentThread]);
}else{
NSLog(@"结束 -- %@",[NSThread currentThread]);
}
dispatch_semaphore_signal(weakSelf.semaphore);
}
@end
//输出结果
2021-05-25 15:08:08.528786+0800 DJGCDDemo[10226:142521] 1 = {number = 6, name = (null)}
2021-05-25 15:08:08.528953+0800 DJGCDDemo[10226:142524] 0 = {number = 7, name = (null)}
2021-05-25 15:08:08.529088+0800 DJGCDDemo[10226:142528] 结束 -- {number = 5, name = (null)}
2021-05-25 15:08:08.529204+0800 DJGCDDemo[10226:142523] 结束 -- {number = 4, name = (null)}
2021-05-25 15:08:08.529319+0800 DJGCDDemo[10226:142526] 结束 -- {number = 3, name = (null)}
(3)Dispatch Semaphore控制多线程最大并发数
通过dispatch_semaphore_create(x)
,可以设置信号量的大小,当信号量为0时线程阻塞,当信号量为1是相当于加锁,只支持单线程,当信号量>1时,则在并发队列中,多线程最大并发量即为信号量的大小。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务1");
sleep(1);
NSLog(@"任务1完成");
dispatch_semaphore_signal(semaphore);
});
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务2");
sleep(1);
NSLog(@"任务2完成");
dispatch_semaphore_signal(semaphore);
});
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务3");
sleep(1);
NSLog(@"任务3完成");
dispatch_semaphore_signal(semaphore);
});
//输出结果
2021-05-25 15:13:10.726745+0800 DJGCDDemo[10283:145555] 执行任务2
2021-05-25 15:13:10.726745+0800 DJGCDDemo[10283:145552] 执行任务1
2021-05-25 15:13:11.727338+0800 DJGCDDemo[10283:145555] 任务2完成
2021-05-25 15:13:11.727338+0800 DJGCDDemo[10283:145552] 任务1完成
2021-05-25 15:13:11.727562+0800 DJGCDDemo[10283:145554] 执行任务3
2021-05-25 15:13:12.729907+0800 DJGCDDemo[10283:145554] 任务3完成
8.Dispatch Source
GCD 中除了主要的Dispatch Queue外, 还有不太引人注目的Dispatch Source(信号源)。它是BSD系内核惯有功能kqueue(kernel queue)内核队列 的包装。
Dispatch source是基础数据类型,协调特定底层系统事件的处理
Dispatch source替代了异步回调函数,来处理系统相关的事件。
当事件发生时,Dispatch source会提交block或函数到指定的queue去执行,和手动提交到queue的任务不同,Dispatch source为应用提供连续的事件源。除非你显式地取消,Dispatch source会一直保留与Dispatch Queue的关联。
(1)Dispatch source创建
dispatch_source_create(dispatch_source_type_t type,//dispatch_source类型
uintptr_t handle,//监听底层系统的句柄,一般是一个常数
unsigned long mask,//dispatch_source监听底层事件的类型,一般是一个常数
dispatch_queue_t Nullable queue)//dispatch_queue
- dispatch source不可重入。当dispatch source被挂起或者event handler block正在执行,这时候dispatch source接收到的多个事件会被合并,并且在dispatch source恢复或者正在执行的event handler block已经执行完毕之后,再来处理合并之后的事。
- dispatch source创建的时候是处于未被激活的状态。
- 要想激活dispatch source,可以通过调用
dispatch_activate()
来开始接受事件。 - 在dispatch source未被激活前,可以调用
dispatch_set_target_queue()
来设置目标队列,但是一旦被激活之后,就不能再设置。 - 处于向后兼容,对于未激活和未挂起的dispatch source调用
dispatch_resume()
和调用dispatch_activate()
有想相同作用,当然更好的激活方式是使用dispatch_activate()
。 - 如果目标queue是
DISPATCH_TARGET_QUEUE_DEFAULT
,events handler block将被提交默认的优先级的全局queue上。 - 实际上
dispatch_source_create()
的handle(例如,作为文件描述符、mach por、信号数、进程标识符等)参数和mask 参数,最后都是被传递到dispatch_source_type_t
结构体中。
(2)dispatch source的类型dispatch_source_type_t
typedef const struct dispatch_source_type_t *dispatch_source_type_t;
/*
当同一时间,一个事件的的触发频率很高,那么Dispatch Source会将这些响应以ADD的方式进行累积,然后等系统空闲时最终处理,如果触发频率比较零散,那么Dispatch Source会将这些事件分别响应。
*/
DISPATCH_SOURCE_TYPE_DATA_ADD 自定义的事件,变量增加
DISPATCH_SOURCE_TYPE_DATA_OR 自定义的事件,变量OR
DISPATCH_SOURCE_TYPE_DATA_REPLACE 自定义的事件,变量Replace
DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口发送
DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 内存报警
DISPATCH_SOURCE_TYPE_PROC 进程监听,如进程的退出、创建一个或更多的子线程、进程收到UNIX信号
DISPATCH_SOURCE_TYPE_READ IO操作,如对文件的操作、socket操作的读响应
DISPATCH_SOURCE_TYPE_SIGNAL 接收到UNIX信号时响应
DISPATCH_SOURCE_TYPE_TIMER 定时器
DISPATCH_SOURCE_TYPE_VNODE 文件状态监听,文件被删除、移动、重命名
DISPATCH_SOURCE_TYPE_WRITE IO操作,如对文件的操作、socket操作的写响应
DISPATCH_MACH_SEND_DEAD
(3)dispatch_source_set_event_handler
和dispatch_source_set_event_handler_f
对指定的dispatch source设置event handler,用来响应dispatch source的触发。
void dispatch_source_set_event_handler(dispatch_source_t source, dispatch_block_t _Nullable handler);
void dispatch_source_set_event_handler_f(dispatch_source_t source, dispatch_function_t _Nullable handler);
(4)dispatch_source_set_cancle_handler
和dispatch_source_set_cancle_handler_f
对指定的dispatch source设置cancle handler,用来响应dispatch source的取消。
如果dispatch_source监听的是文件描述符和mach port,那么就需要使用cancle handler。目的是安全的关闭文件描述符和销毁mach port。如果在触发cancle handler之前,文件描述符和mach port就已经被关闭和销毁,就有可能导致竞争条件。
void dispatch_source_set_cancle_handler(dispatch_source_t source, dispatch_block_t _Nullable handler);
void dispatch_source_set_cancle_handler_f(dispatch_source_t source, dispatch_function_t _Nullable handler);
(5)dispatch_source_merge_data
合并数据的类型是 DISPATCH_SOURCE_TYPE_DATA_ADD
,DISPATCH_SOURCE_TYPE_DATA_OR
,DISPATCH_SOURCE_TYPE_DATA_REPLACE
的dispatch sorce,并且提交相应的events handler block到指定的queue。
//如果value为0,不会产生任何影响,也不会提交到相应的events handler block。
void dispatch_source_merge_data(dispatch_source_t source, unsigned long value);
(6)dispatch_source_set_timer
配置dispatch source timer的开始时间(start time),interval(时间间隔),精度(leeway)。
一旦再次调用这个方法,那么之前的source timer数据会被清除。source timer下次触发的时间将会是start参数设置时间。此后每次间隔设置的interval纳秒将会继续触发,直到source timer被取消。
系统可能会延迟调用source timer的触发,以提高功耗和系统性能。对于刚开始source timer允许的最大延迟上限是设置的leeway纳秒。对于start + N * interval时间后触发的time source,上限为MIN(leeway, interval/2), 下限由系统控制。
void dispatch_source_set_timer(dispatch_source_t source,
dispatch_time_t start,
uint64_t intercal,
uint64_t leeway);
(7)dispatch_source_cancle
取消dispatch source,可阻止events handler block继续调用,但不会中断正在处理的events handler block。
当dispatch source的events handler已经完成,那么events handler将会被提交到目标queue。并且此时表明安全的关闭dispatch source的句柄(标志符,即文件描素符或mach port)。
void dispatch_source_cancle(dispatch_source_t source);
(8)dispatch_source_testcancle
测试dispatch source是否被取消。
long dispatch_source_testcancle(dispatch_source_t source);
(9)dispatch_source_set_registration_handler
给指定的dispatch source设置一个registration handler。
(10)dispatch_source_handler
dispatch_source监听事件产生触发的handler。
(11)常用--定时器
GCD的定时器不受RunLoop的Mode影响。
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic , strong)dispatch_source_t timer;
@property(nonatomic , assign)int number;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self startTimer];
}
-(void)startTimer{
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), 1.0 * NSEC_PER_SEC, 0);
__weak typeof(self) weakSelf = self;
dispatch_source_set_event_handler(_timer, ^{
NSLog(@"执行 - %@",[NSThread currentThread]);
weakSelf.number += 1;
if (weakSelf.number == 4) {
[weakSelf cancleTimer];
}
});
dispatch_resume(_timer);
}
-(void)cancleTimer{
if (_timer) {
dispatch_source_cancel(_timer);
_timer = NULL;
}
}
@end
(12)自定义事件
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD,
0, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(source, ^{
size_t estimated = dispatch_source_get_data(source);
NSLog(@"handler");
});
dispatch_resume(source);
dispatch_source_merge_data(source, 1);
9.dispatch_block
监听任务、取消任务,就需要获取对应的 block。
(1)dispatch_block_create
创建
//flags 参数用来设置 block 的标记,block 参数用来设置具体的任务。
dispatch_block_create(dispatch_block_flags_t flags, dispatch_block_t block);
//dispatch_block_flags 类型
DISPATCH_BLOCK_BARRIER,
DISPATCH_BLOCK_DETACHED,
DISPATCH_BLOCK_ASSIGN_CURRENT,
DISPATCH_BLOCK_NO_QOS_CLASS,
DISPATCH_BLOCK_INHERIT_QOS_CLASS,
DISPATCH_BLOCK_ENFORCE_QOS_CLASS
//相比于 dispatch_block_create 函数,这种方式在创建 block 的同时可以指定了相应的优先级。
dispatch_block_create_with_qos_class(dispatch_block_flags_t flags,
dispatch_qos_class_t qos_class, int relative_priority,
dispatch_block_t block);
//qos_class_t 是一种枚举,有以下类型:
QOS_CLASS_USER_INTERACTIVE:user interactive 等级表示任务需要被立即执行,用来在响应事件之后更新 UI,来提供好的用户体验。这个等级最好保持小规模。
QOS_CLASS_USER_INITIATED:user initiated 等级表示任务由 UI 发起异步执行。适用场景是需要及时结果同时又可以继续交互的时候。
QOS_CLASS_DEFAULT:default 默认优先级
QOS_CLASS_UTILITY:utility 等级表示需要长时间运行的任务,伴有用户可见进度指示器。经常会用来做计算,I/O,网络,持续的数据填充等任务。这个任务节能。
QOS_CLASS_BACKGROUND:background 等级表示用户不会察觉的任务,使用它来处理预加载,或者不需要用户交互和对时间不敏感的任务。
QOS_CLASS_UNSPECIFIED:unspecified 未指明
dispatch_queue_t concurrentQuene = dispatch_queue_create("concurrentQuene", DISPATCH_QUEUE_CONCURRENT);
dispatch_block_t block = dispatch_block_create(0, ^{
NSLog(@"normal do some thing...");
});
dispatch_async(concurrentQuene, block);
dispatch_block_t qosBlock = dispatch_block_create_with_qos_class(0, QOS_CLASS_DEFAULT, 0, ^{
NSLog(@"qos do some thing...");
});
dispatch_async(concurrentQuene, qosBlock);
(2)监听block执行结束
dispatch_block_wait
long dispatch_block_wait(dispatch_block_t block, dispatch_time_t timeout);
//因为dispatch_block_wait会阻塞当前线程,所以不应该放在主线程中调用。创建一个新的并队列执行
dispatch_queue_t concurrentQuene = dispatch_queue_create("concurrentQuene", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQuene, ^{
dispatch_queue_t allTasksQueue = dispatch_queue_create("allTasksQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_block_t block = dispatch_block_create(0, ^{
NSLog(@"开始执行");
[NSThread sleepForTimeInterval:3];
NSLog(@"结束执行");
});
dispatch_async(allTasksQueue, block);
// 等待时长,2s之后超时
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
long resutl = dispatch_block_wait(block, timeout);
if (resutl == 0) {
NSLog(@"执行成功");
} else {
NSLog(@"执行超时");
}
});
//输出结果
2021-05-26 21:32:12.929222+0800 DJGCDDemo[1434:51536] 开始执行
2021-05-26 21:32:14.930320+0800 DJGCDDemo[1434:51372] 执行超时
2021-05-26 21:32:15.931331+0800 DJGCDDemo[1434:51536] 结束执行
dispatch_block_notify
//第一个Block是操作的Block, 第二个Block是完成操作之后的Block
dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue,
dispatch_block_t notification_block);
NSLog(@"---- 开始设置任务 ----");
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t taskBlock = dispatch_block_create(0, ^{
NSLog(@"开始任务");
[NSThread sleepForTimeInterval:2];
NSLog(@"完成任务");
});
dispatch_async(serialQueue, taskBlock);
dispatch_block_t refreshUI = dispatch_block_create(0, ^{
NSLog(@"更新UI");
});
dispatch_block_notify(taskBlock, dispatch_get_main_queue(), refreshUI);
NSLog(@"---- 完成设置任务 ----");
//输出结果
2021-05-26 21:37:13.624975+0800 DJGCDDemo[1463:54640] ---- 开始设置任务 ----
2021-05-26 21:37:13.625308+0800 DJGCDDemo[1463:54640] ---- 完成设置任务 ----
2021-05-26 21:37:13.625308+0800 DJGCDDemo[1463:54850] 开始任务
2021-05-26 21:37:15.630775+0800 DJGCDDemo[1463:54850] 完成任务
2021-05-26 21:37:15.631222+0800 DJGCDDemo[1463:54640] 更新UI
(3)block取消dispatch_block_cancel
这个函数用异步的方式取消指定的 block。 取消操作使将来执行dispatch block立即返回,但是对已经在执行的dispatch block没有任何影响。
当一个block被取消时,它会立即释放捕获的资源。 如果要在一个block中对某些对象进行释放操作,在取消这个block的时候,需要确保内存不会泄漏。
//取消当前已获取资源但尚未执行的Block
void dispatch_block_cancel(dispatch_block_t block);
// 测试Block是否成功的取消了
long dispatch_block_testcancel(dispatch_block_t block);
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t firstTaskBlock = dispatch_block_create(0, ^{
NSLog(@"开始第一个任务");
[NSThread sleepForTimeInterval:2];
NSLog(@"结束第一个任务");
});
dispatch_block_t secTaskBlock = dispatch_block_create(0, ^{
NSLog(@"开始第二个任务");
[NSThread sleepForTimeInterval:3];
NSLog(@"结束第二个任务");
});
dispatch_async(serialQueue, firstTaskBlock);
dispatch_async(serialQueue, secTaskBlock);
[NSThread sleepForTimeInterval:1];
dispatch_block_cancel(firstTaskBlock);
NSLog(@"尝试过取消第一个任务");
dispatch_block_cancel(secTaskBlock);
NSLog(@"尝试过取消第二个任务");
//输出结果
2021-05-26 21:42:54.883761+0800 DJGCDDemo[1488:58381] 开始第一个任务
2021-05-26 21:42:55.884916+0800 DJGCDDemo[1488:58314] 尝试过取消第一个任务
2021-05-26 21:42:55.885238+0800 DJGCDDemo[1488:58314] 尝试过取消第二个任务
2021-05-26 21:42:56.884597+0800 DJGCDDemo[1488:58381] 结束第一个任务
三、底层原理
1.dispatch_once
//调用dispatch_once_f来处理
void dispatch_once(dispatch_once_t *val, dispatch_block_t block) {
dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
dispatch_once
封装调用了dispatch_once_f
函数,其中通过_dispatch_Block_invoke
来执行block任务。
//invoke是指触发block的具体实现,感兴趣的可以看一下Block_layout的结构体
#define _dispatch_Block_invoke(bb)
((dispatch_function_t)((struct Block_layout *)bb)->invoke)
具体的实现函数dispatch_once_f
:
void dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func) {
struct _dispatch_once_waiter_s * volatile *vval =
(struct _dispatch_once_waiter_s**)val;
struct _dispatch_once_waiter_s dow = { NULL, 0 };
struct _dispatch_once_waiter_s *tail, *tmp;
_dispatch_thread_semaphore_t sema;
if (dispatch_atomic_cmpxchg(vval, NULL, &dow, acquire)) {
_dispatch_client_callout(ctxt, func);
dispatch_atomic_maximally_synchronizing_barrier();
// above assumed to contain release barrier
tmp = dispatch_atomic_xchg(vval, DISPATCH_ONCE_DONE, relaxed);
tail = &dow;
while (tail != tmp) {
while (!tmp->dow_next) {
dispatch_hardware_pause();
}
sema = tmp->dow_sema;
tmp = (struct _dispatch_once_waiter_s*)tmp->dow_next;
_dispatch_thread_semaphore_signal(sema);
}
} else {
dow.dow_sema = _dispatch_get_thread_semaphore();
tmp = *vval;
for (;;) {
if (tmp == DISPATCH_ONCE_DONE) {
break;
}
if (dispatch_atomic_cmpxchgvw(vval, tmp, &dow, &tmp, release)) {
dow.dow_next = tmp;
_dispatch_thread_semaphore_wait(dow.dow_sema);
break;
}
}
_dispatch_put_thread_semaphore(dow.dow_sema);
}
}
首先看一下
dispatch_once
中用的的原子性操作dispatch_atomic_cmpxchg(vval, NULL, &dow, acquire)
,它的宏定义展开之后会将$dow赋值给vval,如果vval的初始值为NULL,返回YES,否则返回NO。
接着结合上面的流程图来看下dispatch_once
的代码逻辑:
首次调用dispatch_once
时,因为外部传入的dispatch_once_t
变量值为nil,故vval会为NULL,故if判断成立。然后调用_dispatch_client_callout
执行block,然后在block执行完成之后将vval的值更新成DISPATCH_ONCE_DONE
表示任务已完成。最后遍历链表的节点并调用_dispatch_thread_semaphore_signal
来唤醒等待中的信号量;
当其他线程同时也调用dispatch_once
时,因为if判断是原子性操作,故只有一个线程进入到if分支中,其他线程会进入else分支。在else分支中会判断block是否已完成,如果已完成则跳出循环;否则就是更新链表并调用_dispatch_thread_semaphore_wait
阻塞线程,等待if分支中的block完成后再唤醒当前等待的线程。
- 总结:
dispatch_once
用原子性操作block执行完成标记位,同时用信号量确保只有一个线程执行block,等block执行完再唤醒所有等待中的线程。