021*:GCD 【dispatch_async dispatch_queue_t】 dispatch_group_t、dispatch_barrier_async 、dispatch_semaphore_t、dispatch_once_t、dispatch_apply、dispatch_after、dispatch_source_t 、pthread 、NSThread、NSOperation
问题
dispatch_sync
将任务 block
通过 push
到队列中,然后按照 FIFO
去执行。
dispatch_sync
造成死锁的主要原因是堵塞的tid
和现在运行的tid
为同一个
dispatch_async
会把任务包装并保存,之后就会开辟相应线程去执行已保存的任务。
semaphore
主要在底层维护一个value
的值,使用 signal
进行 + +1
,wait
进行-1
。如果value
的值大于或者等于0,则取消阻塞,否则根据timeout
参数进行超时判断
dispatch_group
底层也是维护了一个 value
的值,等待 group
完成实际上就是等待value
恢复初始值。而notify
的作用是将所有注册的回调组装成一个链表,在 dispatch_async
完成时判断 value
是不是恢复初始值,如果是则调用dispatch_async
异步执行所有注册的回调。
dispatch_once
通过一个静态变量来标记 block
是否已被执行,同时使用加锁确保只有一个线程能执行,执行完 block
后会唤醒其他所有等待的线程。
目录
1: GCD简介
2:GCD任务和队列
3:GCD基础使用(6+1种情况)
4:函数执行顺序
5: 调度组 dispatch_group_t
6:栅栏函数dispatch_barrier_sync & dispatch_barrier_async
7:信号量:dispatch_semaphore_t
8:GCD单例-dispatch_once_t
9:GCD 快速迭代方法:dispatch_apply
10:GCD 延时执行方法:dispatch_after
11:定时器 dispatch_source_t
12:pthread
12:NSThread
13:NSOperation
预备
正文
一:GCD简介
GCD
,全称Grand Central Dispatch
(中央调度中心),纯C语言
开发,提供了很多强大的函数
。GCD是非常高效的多线程开发方式,它并不是Cocoa框架的一部分
1:GCD的优势:
- GCD是苹果公司为
多核并行
运算提出的解决方案
; - GCD会
自动利用
更多的CPU内核
(比如双核、四核等); - GCD会
自动管理
线程的生命周期
(创建线程、调度任务、销毁线程)。
将任务添加到队列,并指定任务执行的函数
在日常开发中,GCD一般写成下面这种形式
dispatch_async( dispatch_queue_create("com.CJL.Queue", NULL), ^{ NSLog(@"GCD基本使用"); });
将上述代码拆分,方便我们来理解GCD
的核心
主要是由 函数
+ 队列 + 任务
构成
//********GCD基础写法******** //创建任务 dispatch_block_t block = ^{ NSLog(@"hello GCD"); }; //创建串行队列 dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", NULL); //将任务添加到队列,并指定函数执行 dispatch_async(queue, block);
- 使用
dispatch_block_t
创建任务 - 使用
dispatch_queue_t
创建队列 - 将任务添加到队列,并指定执行任务的函数
dispatch_async
注意
这里的任务
是指执行操作
的意思,在使用dispatch_block_t
创建任务时,主要有以下两点说明
-
任务使用
block封装
-
任务的block
没有参数
也没有返回值
【重点】用一句话总结GCD
就是:将任务添加到队列,并指定任务执行的函数
二、GCD任务和队列
在GCD使用中我们只需要做两件事:
1.定义任务。
2.将任务添加到队列中。
所以GCD的核心就是dispatch队列和任务。
相信很多初级开发者会对 GCD 任务和队列之间的关系理解含糊不清,实际上队列只是提供了保存任务的容器。为了更好的理解 GCD,很有必要先了解 任务和 队列的概念。
1:GCD任务:dispatch_block_t
任务就是需要执行的操作,是 GCD 中放在 block 中在线程中执行的那段代码。任务的执行的方式有 同步执行和 异步执行两中执行方式。两者的主要区别是 是否等待队列的任务执行结束,以及 是否具备开启新线程的能力。
- 同步执行(sync):同步添加任务到队列中,在队列之前的任务执行结束之前会一直等待;同步执行的任务只能在当前线程中执行,不具备开启新线程的能力。
- 异步执行(async):异步添加任务到队列中,并需要理会队列中其他的任务,添加即执行;异步执行可以在新的线程中执行,具备开启新的线程的能力。
1.使用dispatch_block_t
创建任务
在使用dispatch_block_t
创建任务时,主要有以下两点说明
-
任务使用
block封装
-
任务的block
没有参数
也没有返回值
任务创建好后,等待执行任务的函数
将其放入队列
中。
拓展:
执行block,需要调用block()
,这步调用,是执行任务的函数
内部自动管理
。
后面解析dispatch源码
时,可以清楚
知道调用时机
。
2: GCD 队列:dispatch_queue_t
1:串行队列 和 并发队列
队列(Dispatch Queue)
是指执行任务的等待队列
,即用来存放任务的队列。队列是一种特殊的线性表
,遵循先进先出(FIFO)原则
,即新任务总是被插入到队尾
,而任务的读取从队首开始读取
。每读取一个任务,则动队列中释放一个任务,如下图所示在GCD中,队列主要分为串行队列(Serial Dispatch Queue)
和并发队列(Concurrent Dispatch Queue)
两种,如下图所示
两者的主要区别是:执行顺序不同,以及开启线程数不同。
- 串行队列:只开启一个线程,每次只能有一个任务执行,等待执行完毕后才会执行下一个任务。(类似
单车道
,汽车只能一辆辆
排队通过
) - 并发队列:可以让对个任务同时执行,也就是开启多个线程,让多个任务同时执行。(类似
多车道
,同时可以多辆
汽车通过
)
2:队列的创建方法
可以通过 dispatch_queue_create
来创建队列,源码如下:
API_AVAILABLE(macos(10.6), ios(4.0)) DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT DISPATCH_NOTHROW dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
DISPATCH_QUEUE_SERIAL
表示串行队列,DISPATCH_QUEUE_CONCURRENT
表示并发队列。2.1:创建一个串行队列
dispatch_queue_t seialQueue = dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL);
2.2:创建一个并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);
2.3:主队列(Main Dispatch Queue)一个特殊的串行队列:所有放在主队列中的任务,都会放到主线程中执行。获取方法如下:
dispatch_queue_t mainQueue = dispatch_get_main_queue();
2.4:全局并发队列:(Global Dispatch Queue)
API_AVAILABLE(macos(10.6), ios(4.0)) DISPATCH_EXPORT DISPATCH_CONST DISPATCH_WARN_RESULT DISPATCH_NOTHROW dispatch_queue_global_t dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);
可以看到,获取全局队列也需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用 0 即可。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3 :执行任务的函数
执行任务的函数
包括同步函数
和异步函数
两种:
3.1 :dispatch_sync
同步函数:
- 必须
等待
当前语句执行完毕
,才
会执行下一条
语句 不
会开启线程
,就在当前线程
执行block任务
void dispatch_sync(dispatch_queue_t dq, dispatch_block_t work) { uintptr_t dc_flags = DC_FLAG_BLOCK; if (unlikely(_dispatch_block_has_private_data(work))) { return _dispatch_sync_block_with_privdata(dq, work, dc_flags); } _dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags); }
3.2: dispatch_async
异步函数:
不用等待
当前语句执行完毕
,就可以执行下一条语句会
开启线程执行block
任务(在新线程
执行还是空闲
的旧线程
执行,取决
于cpu的调度
)
dispatch_async(dispatch_queue_t dq, dispatch_block_t work) { dispatch_continuation_t dc = _dispatch_continuation_alloc(); uintptr_t dc_flags = DC_FLAG_CONSUME; dispatch_qos_t qos; // 任务包装器 - 接受 - 保存 - 函数式 // 保存 block qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags); _dispatch_continuation_async(dq, dc, qos, dc->dc_flags); }
3.3:异步
是多线程
的代名词
多线程
的意义
,就是为了适当提高
执行效率
,开启多个线程"同时"
执行多个任务
。
- 严格来说,应该是
并行异步
是多线程
的代名词。因为只有并行
,才支持多通道
(车道),才能同时
执行多个任务
。
//异步执行任务 dispatch_async(queue, ^{ //异步执行的代码 }); //同步执行 dispatch_sync(queue, ^{ //同步执行的代码 });
4:任务和队列的组合
GCD
为开发者提供了两种队列(串行/并发)以及两种任务执行方法(同步/异步),所以这里面就有了四种组合。再结合上面提到的主队列和全局并发队列(可以当作是普通的并发队列),现在就有了6种组合:创建的任务需要放在队列中去执行,同时考虑到主队列的特殊性,那么在不考虑嵌套任务的情况下就会存在同步任务+串行队列、同步任务+并发队列、异步任务+串行队列、异步任务+并发队列、主队列+同步任务、主队列+异步任务六种组合,下面我们来分析下这几种组合。
1. 同步任务+串行队列:同步任务不会开启新的线程,任务串行执行。
2. 同步任务+并发队列:同步任务不会开启新的线程,虽然任务在并发队列中,但是系统只默认开启了一个主线程,没有开启子线程,所以任务串行执行。
3. 异步任务+串行队列:异步任务有开启新的线程,任务串行执行。
4. 异步任务+并发队列:异步任务有开启新的线程,任务并发执行。
5. 主队列+同步任务:主队列是一种串行队列,任务在主线程中串行执行,将同步任务添加到主队列中会造成追加的同步任务和主线程中的任务相互等待阻塞主线程,导致死锁。
6. 主队列+异步任务:主队列是一种串行队列,任务在主线程中串行执行,即使是追加的异步任务也不会开启新的线程,任务串行执行
5:死锁情况
dispatch_queue_t queue = dispatch_queue_create("com.thread.demo", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ // 异步任务 + 串行队列 dispatch_sync(queue, ^{ // 同步任务 + 串行队列 sleep(1); // 模拟耗时操作 NSLog(@"1"); }); });
执行上面的代码会导致 串行队列中追加的任务 和 串行队列中原有的任务 两者之间相互等待,阻塞了串行队列,最终造成了串行队列所在的线程(子线程)死锁问题。主队列造成死锁也是基于这个原因,所以,这也进一步说明了主队列其实并不特殊。
三:GCD基础使用(6+1种情况)
1 :同步任务+串行队列
不会开启新的线程,任务串行执行的。
- (void)syncSerial { NSLog(@"当前线程:%@", [NSThread currentThread]); NSLog(@"串行队列+同步执行---开始"); dispatch_queue_t queue = dispatch_queue_create("sync.serial", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ //任务1 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务1所在的线程:%@", [NSThread currentThread]); } }); dispatch_sync(queue, ^{ //任务2 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务2所在的线程:%@", [NSThread currentThread]); } }); dispatch_sync(queue, ^{ //任务3 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务3所在的线程:%@", [NSThread currentThread]); } }); NSLog(@"串行队列+同步执行---结束"); }
运行项目,输出结果如下:
可以看到:
- 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)
- 所有任务都是在
串行队列+同步执行---开始
之后,串行队列+同步执行---结束
之前(同步任务需要等待队列的任务执行结束之后才能执行) - 任务是按照顺序先后执行的(串行队列每次只有一个任务被执行,执行完一个任务,才会执行下一步操作)
2:同步任务+并发队列
在当前线程中执行任务,不会开启新的线程,执行完一个任务,再执行另一个任务,串行执行
- (void)syncConcurrent { NSLog(@"当前线程:%@", [NSThread currentThread]); NSLog(@"同步执行+并发队列---开始"); dispatch_queue_t queue = dispatch_queue_create("sync.concurrent", DISPATCH_QUEUE_CONCURRENT); dispatch_sync(queue, ^{ //任务1 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务1所在的线程:%@", [NSThread currentThread]); } }); dispatch_sync(queue, ^{ //任务2 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务2所在的线程:%@", [NSThread currentThread]); } }); dispatch_sync(queue, ^{ //任务3 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务3所在的线程:%@", [NSThread currentThread]); } }); NSLog(@"同步执行+并发队列---结束"); }
运行项目,输出结果如下:
可以看到:
- 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)
- 所有任务的打印都是在
并发队列+同步执行---开始
之后,并发队列+同步执行---结束
之前(同步任务需要等待队列的任务执行结束之后才能执行) - 任务是按照先后顺序执行的。虽然并发队列可以开启多个线程,并且同时执行多个任务,但是同步任务不具备开启新线程的能力,所以只有当前线程这一个线程,也就不存在并发。当前线程只有等待当前队列中正在执行的任务执行完毕,才能继续执行下一步操作,所以任务只能一个个按照顺序执行。串行执行
3:异步执行+串行队列
具备开启新线程的能力,但是因为任务是在串行队列中执行,当前任务执行完毕,才会执行下一个任务
- (void)asyncSerial { NSLog(@"当前线程:%@", [NSThread currentThread]); NSLog(@"串行队列+异步执行---开始"); dispatch_queue_t queue = dispatch_queue_create("async.serial", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ //任务1 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务1所在的线程:%@", [NSThread currentThread]); } }); dispatch_async(queue, ^{ //任务2 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务2所在的线程:%@", [NSThread currentThread]); } }); dispatch_async(queue, ^{ //任务3 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务3所在的线程:%@", [NSThread currentThread]); } }); NSLog(@"串行队列+异步执行---结束"); }
运行项目,输出结果如下:
可以看到:
- 开启了新的线程(异步执行具备开启新线程的能力,串行队列只开启一个新线程)
- 所有任务都是在
串行队列+异步执行---开始
和串行队列+异步执行---结束
之后开始执行的(异步执行不做任何等待,可以继续执行) - 任务是按照先后顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个顺序执行)串行执行
4:异步执行+并发队列
可以开启多个线程,任务交替执行(即我们肉眼看到的同时执行),并发执行
- (void)asyncConcurrent { NSLog(@"当前线程:%@", [NSThread currentThread]); NSLog(@"并发队列+异步执行---开始"); dispatch_queue_t queue = dispatch_queue_create("async.concurrent", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ //任务1 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务1所在的线程:%@", [NSThread currentThread]); } }); dispatch_async(queue, ^{ //任务2 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务2所在的线程:%@", [NSThread currentThread]); } }); dispatch_async(queue, ^{ //任务3 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务3所在的线程:%@", [NSThread currentThread]); } }); NSLog(@"并发队列+异步执行---结束"); }
运行项目,输出结果如下:
可以看到:
- 除了当前线程(主线程),系统重新开辟了 3 个线程(异步执行具备开启新线程的能力)
- 任务是交替执行的(并发队列可开启多个线程,
同时
执行多个任务) - 所有任务的执行都是在
并发队列+异步执行---开始
和并发队列+异步执行---结束
之后,说明了当前线程并没有等待,而是直接开启了新的线程,在新的线程中执行任务(异步执行不做等待,可以继续执行任务)
主队列
- 所有放在主队列中的任务,都会放到主线程中执行
- 获取:
dispatch_get_main_queue()
同步执行 + 主队列在不同线程中调用结果也是不一样,在主线程中调用会出现死锁,而在其他线程中则不会。
在主线程 中调用 主队列+同步执行
互相等待(死锁)
- (void)syncMain { NSLog(@"当前线程:%@", [NSThread currentThread]); NSLog(@"在主线程中主队列+同步执行---开始"); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_sync(queue, ^{ //任务1 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务1所在的线程:%@", [NSThread currentThread]); } }); dispatch_sync(queue, ^{ //任务2 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务2所在的线程:%@", [NSThread currentThread]); } }); dispatch_sync(queue, ^{ //任务3 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务3所在的线程:%@", [NSThread currentThread]); } }); NSLog(@"在主线程中主队列+同步执行---结束"); }
可以看到:
在同步执行 + 主队列可以惊奇的发现:
在主线程中使用同步执行 + 主队列,追加到主线程的任务1、任务2、任务3都不再执行了,而且syncMain---end
也没有打印,还会报崩溃。这是为什么呢?
这是因为我们在主线程中执行syncMain
方法,相当于把syncMain
任务放到了主线程的队列中。而同步执行会等待当前队列中的任务执行完毕,才会接着执行。那么当我们把任务1追加到主队列中,任务1就在等待主线程处理完syncMain
任务。而syncMain任务需要等待任务1执行完毕,才能接着执行。
那么,现在的情况就是syncMain
任务和任务1都在等对方执行完毕。这样大家互相等待,所以就卡住了,所以我们的任务执行不了,而且syncMain---end
也没有打印。
6:异步执行+主队列
只在主线程中执行任务,执行完一个任务,再执行下一个任务,串行执行
- (void)asyncMain { NSLog(@"当前线程:%@", [NSThread currentThread]); NSLog(@"主队列+异步执行---开始"); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_async(queue, ^{ //任务1 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务1所在的线程:%@", [NSThread currentThread]); } }); dispatch_async(queue, ^{ //任务2 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务2所在的线程:%@", [NSThread currentThread]); } }); dispatch_async(queue, ^{ //任务3 for (int i = 0; i < 2; i++) { sleep(2); //模拟耗时操作 NSLog(@"任务3所在的线程:%@", [NSThread currentThread]); } }); NSLog(@"主队列+异步执行---结束"); }
运行项目,输出结果如下:
可以看到:
- 所有任务都是在主线程中执行的,并没有开启新线程(异步执行具备开启新线程的能力,但因为在主队列,所有任务都是在主线程中)
- 所有任务都是在
主队列+异步执行---开始
和主队列+异步执行---结束
之后执行(异步执行不做任何等待,可以继续执行任务) - 任务是按照顺序执行的(主队列是串行队列,每次只有一个任务被执行,一个任务执行完毕,才会执行下一个任务),串行执行
7:在其他线程中调用同步执行 + 主队列,相当于异步执行主队列
不会开启新线程,执行完一个任务,再执行下一个任务。
// 使用 NSThread 的 detachNewThreadSelector 方法会创建线程,并自动启动线程执行 selector 任务 [NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil]; 输出结果: 2018-02-23 20:44:19.377321+0800 YSC-GCD-demo[20083:5040347] currentThread---<NSThread: 0x600000272fc0>{number = 3, name = (null)} 2018-02-23 20:44:19.377494+0800 YSC-GCD-demo[20083:5040347] syncMain---begin 2018-02-23 20:44:21.384716+0800 YSC-GCD-demo[20083:5040132] 1---<NSThread: 0x60000006c900>{number = 1, name = main} 2018-02-23 20:44:23.386091+0800 YSC-GCD-demo[20083:5040132] 1---<NSThread: 0x60000006c900>{number = 1, name = main} 2018-02-23 20:44:25.387687+0800 YSC-GCD-demo[20083:5040132] 2---<NSThread: 0x60000006c900>{number = 1, name = main} 2018-02-23 20:44:27.388648+0800 YSC-GCD-demo[20083:5040132] 2---<NSThread: 0x60000006c900>{number = 1, name = main} 2018-02-23 20:44:29.390459+0800 YSC-GCD-demo[20083:5040132] 3---<NSThread: 0x60000006c900>{number = 1, name = main} 2018-02-23 20:44:31.391965+0800 YSC-GCD-demo[20083:5040132] 3---<NSThread: 0x60000006c900>{number = 1, name = main} 2018-02-23 20:44:31.392513+0800 YSC-GCD-demo[20083:5040347] syncMain---end
在其他线程中使用同步执行 + 主队列可看到:
- 所有任务都是在主线程(非当前线程)中执行的,没有开启新的线程(所有放在主队列中的任务,都会放到主线程中执行)。
- 所有任务都在打印的
syncConcurrent---begin
和syncConcurrent---end
之间执行(同步任务需要等待队列的任务执行结束)。 - 任务是按顺序执行的(主队列是串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。
为什么现在就不会卡住了呢?
因为syncMain任务放到了其他线程里,而任务1、任务2、任务3都在追加到主队列中,这三个任务都会在主线程中执行。syncMain 任务在其他线程中执行到追加任务1到主队列中,因为主队列现在没有正在执行的任务,所以,会直接执行主队列的任务1,等任务1执行完毕,再接着执行任务2、任务3。所以这里不会卡住线程。
四:函数执行顺序
1:异步函数+并行队列
- (void)interview01{ //并行队列 dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT); NSLog(@"1"); // 耗时 dispatch_async(queue, ^{ NSLog(@"2"); dispatch_async(queue, ^{ NSLog(@"3"); }); NSLog(@"4"); }); NSLog(@"5"); } ----------打印结果----------- 输出顺序为:1 5 2 4 3
异步函数
并不会阻塞
主队列,会开辟新线程
执行异步任务
-
主线程
的任务队列为:任务1、异步block1、任务5
,其中异步block1
会比较耗费性能,任务1和任务5的任务复杂度是相同的,所以任务1和任务5优先于异步block1执行
-
在
异步block1
中,任务队列为:任务2、异步block2、任务4
,其中block2
相对比较耗费性能,任务2
和任务4
是复杂度一样,所以任务2和任务4优先于block2执行
-
最后执行
block2
中的任务3
-
在极端情况下,可能出现
任务2
先于任务1
和任务5
执行,原因是出现了当前主线程卡顿
或者延迟
的情况
代码修改
-
【修改1】:将
并行队列
改成串行队列
,对结果没有任何影响,顺序仍然是1 5 2 4 3
-
【修改2】:在任务5之前,休眠2s,即
sleep(2)
,执行的顺序为:1 2 4 3 5
,原因是因为I/O的打印,相比于休眠2s,复杂度更简单,所以异步block1 会先于任务5执行。当然如果主队列堵塞,会出现其他的执行顺序
2:异步函数嵌套同步函数 + 并发队列
- (void)interview02{ //并发队列 dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT); NSLog(@"1"); //异步函数 dispatch_async(queue, ^{ NSLog(@"2"); //同步函数 dispatch_sync(queue, ^{ NSLog(@"3"); }); NSLog(@"4"); }); NSLog(@"5"); } ----------打印结果----------- 输出顺序为:1 5 2 3 4
分析
-
任务1 和 任务5的分析同前面一致,执行顺序为
任务1 任务5 异步block
-
在异步block中,首先执行
任务2
,然后走到同步block
,由于同步函数会阻塞主线程
,所以任务4需要等待任务3执行完成
后,才能执行,所以异步block中的执行顺序是:任务2 任务3 任务4
3:异步函数嵌套 同步函数 + 串行队列(即同步队列)
开辟线程了。
- (void)interview03{ // 同步队列 dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", NULL); NSLog(@"1"); // 异步函数 dispatch_async(queue, ^{ NSLog(@"2"); // 同步函数 dispatch_sync(queue, ^{ NSLog(@"3"); }); NSLog(@"4"); }); NSLog(@"5"); } ----------打印结果----------- 输出顺序为:1 5 2 死锁崩溃
分析
-
首先执行
任务1
,接下来是异步block,并不会阻塞主线程
,相比任务5而言,复杂度更高,所以优先执行任务5
,在执行异步block -
在
异步block
中,先执行任务2
,接下来是同步block
,同步函数会阻塞线程
,所以执行任务4需要等待任务3执行完成
,而任务3
的执行,需要等待异步block
执行完成,相当于任务3等待任务4完成
-
所以就造成了
任务4等待任务3,任务3等待任务4
,即互相等待
的局面,就会造成死锁
,这里有个重点是关键的堆栈 slow
五:调度组 dispatch_group_t
与栅栏函数
类似,也是控制
任务的执行顺序
。
dispatch_group_create
创建组dispatch_group_async
进组任务 (自动管理进组
和出组
)dispatch_group_notify
进组任务执行完毕通知dispatch_group_wait
进组任务执行等待时间
-
dispatch_group_enter
进组 -
dispatch_group_leave
出组进组
和出组
需要成对搭配
使用
有以下两种使用方式
- 【方式一】使用
dispatch_group_async + dispatch_group_notify,
// 2. 【自动入组和出组】
- (void)cjl_testGroup1{ /* dispatch_group_t:调度组将任务分组执行,能监听任务组完成,并设置等待时间 应用场景:多个接口请求之后刷新页面 */ dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_group_async(group, queue, ^{ NSLog(@"请求一完成"); }); dispatch_group_async(group, queue, ^{ NSLog(@"请求二完成"); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"刷新页面"); }); }
- 【方式二】使用
dispatch_group_enter + dispatch_group_leave + dispatch_group_notify,
// 1. 【手动入组和出组】
- (void)cjl_testGroup2{ /* dispatch_group_enter和dispatch_group_leave成对出现,使进出组的逻辑更加清晰 */ dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_group_enter(group); dispatch_async(queue, ^{ NSLog(@"请求一完成"); dispatch_group_leave(group); }); dispatch_group_enter(group); dispatch_async(queue, ^{ NSLog(@"请求二完成"); dispatch_group_leave(group); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"刷新界面"); }); }
- 在方式二的基础上增加超时
dispatch_group_wait
- (void)cjl_testGroup3{ /* long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout) group:需要等待的调度组 timeout:等待的超时时间(即等多久) - 设置为DISPATCH_TIME_NOW意味着不等待直接判定调度组是否执行完毕 - 设置为DISPATCH_TIME_FOREVER则会阻塞当前调度组,直到调度组执行完毕 返回值:为long类型 - 返回值为0——在指定时间内调度组完成了任务 - 返回值不为0——在指定时间内调度组没有按时完成任务 */ dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_group_enter(group); dispatch_async(queue, ^{ NSLog(@"请求一完成"); dispatch_group_leave(group); }); dispatch_group_enter(group); dispatch_async(queue, ^{ NSLog(@"请求二完成"); dispatch_group_leave(group); }); // long timeout = dispatch_group_wait(group, DISPATCH_TIME_NOW); // long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER); long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 *NSEC_PER_SEC)); NSLog(@"timeout = %ld", timeout); if (timeout == 0) { NSLog(@"按时完成任务"); }else{ NSLog(@"超时"); } dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"刷新界面"); }); }
探讨: dispatch_group_notify内部是异步的执行 dispatch_group_notify队列:决定该block在哪个线程中处理(主:主线程 非主队列:子线程) dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"++++++++%@",[NSThread currentThread]); });
dispatch_group_enter
、dispatch_group_leave
相关代码运行结果中可以看出:当所有任务执行完成之后,才执行 dispatch_group_notify
中的任务。这里的dispatch_group_enter
、dispatch_group_leave
组合,其实等同于dispatch_group_async
。dispatch_group_wait
会阻塞当前线程。leave
多了时,直接crash!
dispatch_group_enter
减一dispatch_group_leave
加一,值为DISPATCH_GROUP_VALUE_1
时,会执行一个do-while循环处理新旧状态值,最终dispatch_group_leave
会调用_dispatch_group_wake
通知group
的任务block
继续往下执行。六:栅栏函数dispatch_barrier_sync & dispatch_barrier_async
栅栏函数,主要有两种使用场景:串行队列、并发队列
控制
任务执行顺序
,同步
。
-
dispatch_barrier_async
: 前面任务都执行完毕,才会到这里(不会堵塞线程) -
dispatch_barrier_sync
: 堵塞线程,等待前面任务都执行完毕,才放开堵塞。堵塞期间,后面的任务都被挂起等待。
重点:栅栏函数只能控制同一并发队列
栅栏函数
只应用在并行
队列&异步
函数中,它的作用就是在监听
多个信号(任务)
是否都完成
。
(串行
或同步
内的信号(任务)
本身就是按顺序执行
,不需要使用到栅栏函数
。)
坑点:栅栏函数为何不能使用dispatch_get_global_queue
队列?
因为global
队列中有很多系统任务
也在执行
。 我们需要dispatch_queue_create
手动创建一个纯净
的队列
,放置自己
需要执行的任务
,再使用栅栏函数
监听任务的执行结果
。
/** * 栅栏方法 dispatch_barrier_async */ - (void)barrier { dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ // 追加任务1 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 } }); dispatch_async(queue, ^{ // 追加任务2 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程 } }); dispatch_barrier_async(queue, ^{ // 追加任务 barrier for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程 } }); dispatch_async(queue, ^{ // 追加任务3 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程 } }); dispatch_async(queue, ^{ // 追加任务4 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程 } }); } 输出结果: 2018-02-23 20:48:18.297745+0800 YSC-GCD-demo[20188:5059274] 1---<NSThread: 0x600000079d80>{number = 4, name = (null)} 2018-02-23 20:48:18.297745+0800 YSC-GCD-demo[20188:5059273] 2---<NSThread: 0x600000079e00>{number = 3, name = (null)} 2018-02-23 20:48:20.301139+0800 YSC-GCD-demo[20188:5059274] 1---<NSThread: 0x600000079d80>{number = 4, name = (null)} 2018-02-23 20:48:20.301139+0800 YSC-GCD-demo[20188:5059273] 2---<NSThread: 0x600000079e00>{number = 3, name = (null)} 2018-02-23 20:48:22.306290+0800 YSC-GCD-demo[20188:5059274] barrier---<NSThread: 0x600000079d80>{number = 4, name = (null)} 2018-02-23 20:48:24.311655+0800 YSC-GCD-demo[20188:5059274] barrier---<NSThread: 0x600000079d80>{number = 4, name = (null)} 2018-02-23 20:48:26.316943+0800 YSC-GCD-demo[20188:5059273] 4---<NSThread: 0x600000079e00>{number = 3, name = (null)} 2018-02-23 20:48:26.316956+0800 YSC-GCD-demo[20188:5059274] 3---<NSThread: 0x600000079d80>{number = 4, name = (null)} 2018-02-23 20:48:28.320660+0800 YSC-GCD-demo[20188:5059273] 4---<NSThread: 0x600000079e00>{number = 3, name = (null)} 2018-02-23 20:48:28.320649+0800 YSC-GCD-demo[20188:5059274] 3---<NSThread: 0x600000079d80>{number = 4, name = (null)}
七:信号量:dispatch_semaphore_t
控制GCD
的最大并发数
。(同一时刻
可进行的信号(任务)
最大个数。)
1:dispatch_semaphore_create
: 创建信号量
2:dispatch_semaphore_wait
: 信号量等待,发送一个等待信号,信号量-1,当信号量为0阻塞线程,大于0则开始执行后面的逻辑(也就是说执行dispatch_semaphore_wait
前如果信号量 <=0 则阻塞,否则正常执行后面的逻辑)当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。
3:dispatch_semaphore_signal
: 信号量释放发送唤醒信号,信号量会+1
加入了信号量的等待dispatch_semaphore_wait
后,一定需要配对
加入信号量释放dispatch_semaphore_signal
,不然会crash
信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。
Dispatch Semaphore
在实际开发中主要用于:
- 保持线程同步,将异步执行任务转换为同步执行任务
- 保证线程安全,为线程加锁
1:Dispatch Semaphore 线程同步
dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_semaphore_t sem = dispatch_semaphore_create(1); //任务1 dispatch_async(queue, ^{ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); NSLog(@"执行任务1"); sleep(1); NSLog(@"任务1完成"); dispatch_semaphore_signal(sem); }); //任务2 dispatch_async(queue, ^{ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); NSLog(@"执行任务2"); sleep(1); NSLog(@"任务2完成"); dispatch_semaphore_signal(sem); }); //任务3 dispatch_async(queue, ^{ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); NSLog(@"执行任务3"); sleep(1); NSLog(@"任务3完成"); dispatch_semaphore_signal(sem); });
信号量的发送与等待,配合使用,可以保证当前任务的按一定顺序去执行,实现线程同步。
2:Dispatch Semaphore 线程安全和线程同步(为线程加锁)
线程安全
:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。
线程同步
:可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。
下面,我们模拟火车票售卖的方式,实现NSThread 线程安全和解决线程同步问题。
场景
:总共有50张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。
/** * 线程安全:使用 semaphore 加锁 * 初始化火车票数量、卖票窗口(线程安全)、并开始卖票 */ - (void)initTicketStatusSave { 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:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]); [NSThread sleepForTimeInterval:0.2]; } else { //如果已卖完,关闭售票窗口 NSLog(@"所有火车票均已售完"); // 相当于解锁 dispatch_semaphore_signal(semaphoreLock); break; } // 相当于解锁 dispatch_semaphore_signal(semaphoreLock); } } 输出结果为: 2018-02-23 22:32:19.814232+0800 YSC-GCD-demo[20862:5290531] currentThread---<NSThread: 0x6000000783c0>{number = 1, name = main} 2018-02-23 22:32:19.814412+0800 YSC-GCD-demo[20862:5290531] semaphore---begin 2018-02-23 22:32:19.814837+0800 YSC-GCD-demo[20862:5290687] 剩余票数:49 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)} 2018-02-23 22:32:20.017745+0800 YSC-GCD-demo[20862:5290689] 剩余票数:48 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)} 2018-02-23 22:32:20.222039+0800 YSC-GCD-demo[20862:5290687] 剩余票数:47 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)} ... 2018-02-23 22:32:29.024817+0800 YSC-GCD-demo[20862:5290689] 剩余票数:4 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)} 2018-02-23 22:32:29.230110+0800 YSC-GCD-demo[20862:5290687] 剩余票数:3 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)} 2018-02-23 22:32:29.433615+0800 YSC-GCD-demo[20862:5290689] 剩余票数:2 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)} 2018-02-23 22:32:29.637572+0800 YSC-GCD-demo[20862:5290687] 剩余票数:1 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)} 2018-02-23 22:32:29.840234+0800 YSC-GCD-demo[20862:5290689] 剩余票数:0 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)} 2018-02-23 22:32:30.044960+0800 YSC-GCD-demo[20862:5290687] 所有火车票均已售完 2018-02-23 22:32:30.045260+0800 YSC-GCD-demo[20862:5290689] 所有火车票均已售完
可以看出,在考虑了线程安全的情况下,使用 dispatch_semaphore
机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。
八:GCD单例-dispatch_once_t
单例原理
1:利用static
在内存中仅一份
的特性,保证了对象的唯一性
。
2:重写allocWithZone
的实现,让外界使用alloc
创建时,永远返回的是static
声明的对象
。
以下是KCImageManger
的核心代码:
#import "KCImageManger.h" // 保存在常量区 static id instance; @implementation KCImageManger /** 每次类初始化的时候进行调用 1、+load它不遵循那套继承规则。如果某个类本身没有实现+load方法,那么不管其它各级超类是否实现此方法,系统都不会调用。+load方法调用顺序是:SuperClass -->SubClass --> CategaryClass。 3、+initialize是在类或者它的子类接受第一条消息前被调用,但是在它的超类接收到initialize之后。也就是说+initialize是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的+initialize方法是不会被调用的。 4、只有执行+initialize的那个线程可以操作类或类实例,其他线程都要阻塞等着+initialize执行完。 5、+initialize 本身类的调用都会执行父类和分类实现 initialize方法都会被调多次 */ + (void)initialize{ NSLog(@"父类"); if (instance == nil) { instance = [[self alloc] init]; } } /** 配合上面 也能进行单利 */ + (instancetype)manager{ return instance; } /** * 所有为类的对象分配空间的方法,最终都会调用到 allovWithZone 方法 * 下面这样的操作相当于锁死 该类的所有初始化方法 */ +(instancetype)allocWithZone:(struct _NSZone *)zone{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [super allocWithZone:zone]; }); return instance; } /** 单利 */ +(instancetype)shareManager{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; } @end
九:GCD 快速迭代方法:dispatch_apply
通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的函数dispatch_apply
。dispatch_apply
按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
如果是在串行队列中使用 dispatch_apply
,那么就和for循环一样,按顺序同步执行。可这样就体现不出快速迭代的意义了。
我们可以利用并发队列进行异步执行。比如说遍历 0~5 这6个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply
可以 在多个线程中同时(异步)遍历多个数字。
还有一点,无论是在串行队列,还是异步队列中,dispatch_apply
都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的dispatch_group_wait
方法。
1:添加到串行队列中——按序执行
2:添加到主队列中——死锁
3:添加到并发队列中——乱序执行
4:添加到全局队列中——乱序执行
/** * 快速迭代方法 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"); } 输出结果: 2018-02-23 22:03:18.475499+0800 YSC-GCD-demo[20470:5176805] apply---begin 2018-02-23 22:03:18.476672+0800 YSC-GCD-demo[20470:5177035] 1---<NSThread: 0x60000027b8c0>{number = 3, name = (null)} 2018-02-23 22:03:18.476693+0800 YSC-GCD-demo[20470:5176805] 0---<NSThread: 0x604000070640>{number = 1, name = main} 2018-02-23 22:03:18.476704+0800 YSC-GCD-demo[20470:5177037] 2---<NSThread: 0x604000276800>{number = 4, name = (null)} 2018-02-23 22:03:18.476735+0800 YSC-GCD-demo[20470:5177036] 3---<NSThread: 0x60000027b800>{number = 5, name = (null)} 2018-02-23 22:03:18.476867+0800 YSC-GCD-demo[20470:5177035] 4---<NSThread: 0x60000027b8c0>{number = 3, name = (null)} 2018-02-23 22:03:18.476867+0800 YSC-GCD-demo[20470:5176805] 5---<NSThread: 0x604000070640>{number = 1, name = main} 2018-02-23 22:03:18.477038+0800 YSC-GCD-demo[20470:5176805] apply---end
因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是apply---end一定在最后执行。这是因为dispatch_apply函数会等待全部任务执行完毕。
十:GCD 延时执行方法:dispatch_after
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]); // 打印当前线程 }); } 输出结果: 2018-02-23 20:53:08.713784+0800 YSC-GCD-demo[20282:5080295] currentThread---<NSThread: 0x60000006ee00>{number = 1, name = main} 2018-02-23 20:53:08.713962+0800 YSC-GCD-demo[20282:5080295] asyncMain---begin 2018-02-23 20:53:10.714283+0800 YSC-GCD-demo[20282:5080295] after---<NSThread: 0x60000006ee00>{number = 1, name = main} 可以看出:在打印 asyncMain---begin 之后大约 2.0 秒的时间,打印了 after---<NSThread: 0x60000006ee00>{number = 1, name = main}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self delay]; } -(void)delay { NSLog(@"--delay-"); //延迟执行 // 方法1: [self performSelector:@selector(run) withObject:nil afterDelay:2.0]; // 方法2: [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO]; //方法3:GCD中的延迟执行 /* 参数说明 * * 第一个参数:设置时间(GCD中的时间单位是纳秒) * 第二个参数:队列(决定block中的任务在哪个线程中执行,如果是主队列就是主线程,否在就在子线程) * 第三个参数:设置任务 * 原理:(哪个简单) * A 先把任务提交到队列,然后等两秒再执行 错误 * B 先等两秒,再把任务提交到队列 正确 */ // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{ // NSLog(@"-----GCD------%@",[NSThread currentThread]); // }); } -(void)run { NSLog(@"run--%@",[NSThread currentThread]); }
十一:定时器 dispatch_source_t
- (void)use033{ //倒计时时间 __block int timeout = 3; //创建队列 dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); //创建timer dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, globalQueue); //设置1s触发一次,0s的误差 /* - source 分派源 - start 数控制计时器第一次触发的时刻。参数类型是 dispatch_time_t,这是一个opaque类型,我们不能直接操作它。我们得需要 dispatch_time 和 dispatch_walltime 函数来创建它们。另外,常量 DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER 通常很有用。 - interval 间隔时间 - leeway 计时器触发的精准程度 */ dispatch_source_set_timer(timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0); //触发的事件 dispatch_source_set_event_handler(timer, ^{ //倒计时结束,关闭 if (timeout <= 0) { //取消dispatch源 dispatch_source_cancel(timer); }else{ timeout--; dispatch_async(dispatch_get_main_queue(), ^{ //更新主界面的操作 NSLog(@"倒计时 - %d", timeout); }); } }); //开始执行dispatch源 dispatch_resume(timer); }
因为dispatch_source不依赖于Runloop,而是直接和底层内核交互,准确性更高。
时间准确,可以使用子线程
十二:pthread
pthread的基本使用
#import "ViewController.h" #import <pthread.h> @interface ViewController () @end @implementation ViewController - (IBAction)btnClick:(id)sender { NSLog(@"mainThread:%@",[NSThread mainThread]); //pthread创建线程,执行任务 //01 包含头文件 //02 创建线程对象 pthread_t thread = nil; //03 创建线程,执行任务 /* 参数说明 * * 第一个参数:线程对象 传地址 * 第二个参数:线程的属性 (优先级) * 第三个参数:指向函数的指针 * 第四个参数:传给第三个参数的(参数) */ pthread_create(&thread, NULL, run, NULL); } //技巧:(*)改写成函数的名称,补全参数 void *run(void *str) { //NSLog(@"run-------%@",[NSThread currentThread]); //把耗时操作放在子线程中执行 for (int i = 0; i < 1000000; ++i) { NSLog(@"%zd---%@",i,[NSThread currentThread]); } return NULL; } //$\color{red}{正在运行}$ @end
十三:NSThread
NSthread
是苹果官方提供面向对象的线程操作技术,是对thread
的上层封装,比较偏向于底层。简单方便,可以直接操作线程对象,使用频率较少。
1:创建线程
线程的创建方式主要以下三种方式
-
通过
init
初始化方式创建 -
通过
detachNewThreadSelector
构造器方式创建 -
通过
performSelector...
方法创建,主要是用于获取主线程
,以及后台线程
//1、创建 - (void)cjl_createNSThread{ NSString *threadName1 = @"NSThread1"; NSString *threadName2 = @"NSThread2"; NSString *threadName3 = @"NSThread3"; NSString *threadNameMain = @"NSThreadMain"; //方式一:初始化方式,需要手动启动 NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:threadName1]; [thread1 start]; //方式二:构造器方式,自动启动 [NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:threadName2]; //方式三:performSelector...方法创建 [self performSelectorInBackground:@selector(doSomething:) withObject:threadName3]; //方式四 [self performSelectorOnMainThread:@selector(doSomething:) withObject:threadNameMain waitUntilDone:YES]; } - (void)doSomething:(NSObject *)objc{ NSLog(@"%@ - %@", objc, [NSThread currentThread]); }
2:主线程相关用法
+ (NSThread *)mainThread; // 获得主线程 - (BOOL)isMainThread; // 是否为主线程 + (BOOL)isMainThread; // 是否为主线程
三种创建线程方式对
方式1:代码量更大|能够拿到线程对象(需要手动开启)
方式2:分离出子线程(不需要手动开启),无法拿到线程对象进行详细设置(名字|优先级)
方式3:开启后台线程 (不需要手动开启),无法拿到线程对象进行详细设置(名字|优先级)
3:线程的属性设置(名称|优先级)
:
//设置线程的名字 - (void)setName:(NSString *)name; - (NSString *)name; //设置线程的优先级 `范围 0.0~1.0` // 默认是0.5 //1.0最高的 //优先级更高的线程,被CPU调度到的概率会更高 - (void)setThreadPriority:(double)priority; - (double)threadPriority;
4:控制线程状态
启动线程 - (void)start; // 进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态 阻塞(暂停)线程 + (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 进入阻塞状态 强制停止线程 + (void)exit; // 进入死亡状态 注意:一旦线程停止(死亡)了,就不能再次开启任务
5:线程之间的通信
/* 参数说明 * * 第一个参数:方法选择器 回到主线程要做什么(方法) * 第二个参数:调用函数需要传递的参数 * 第三个参数:是否等待该方法执行完毕才继续往下执行 YES:等待 */ - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
十四:NSOperation 与 NSOperationQueue
1:NSOperation
的作用
1:配合使用 NSOperation
和 NSOperationQueue
也能实现多线程
编程。
NSOperation
和 NSOperationQueue
实现多线程的具体步骤:
- 先将需要执行的操作或者任务封装到一个
NSOperation
对象中 - 然后将
NSOperation
对象添加到NSOperationQueue
中 - 系统会自动将
NSOperationQueue
中的NSOperation
取出来 - 将取出的
NSOperation
封装的操作放到一条新线程中执行
2:NSOperation
的子类-
NSOperation
是个抽象类,并不具备封装操作的能力,必须使用它的子类。 -
使用
NSOperation
子类有3种:
NSInvocationOperation
NSBlockOperation
- 自定义子类继承
NSOperation
,实现内部相应的方法
3:NSInvocationOperation
- 创建
NSInvocationOperation
对象.-(id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
- 调用
start
方法开始执行操作.-(void)start;
一旦执行操作,就会调用targe
t的sel
方法.
默认情况下,调用了
start
方法后并不会
开一条新线程去执行操作,而是在当前线程同步执行操作
。只有将
NSOperation
放到一个NSOperationQueue
中,才会异步执行操作
。-(void)invocationOperation { //01 封装操作对象 NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil]; //02 执行操作 [op1 start]; }
4:NSBlockOperation
创建NSBlockOperation对象+(id)blockOperationWithBlock:(void (^)(void))block;
-(void)blockOperation { //操作:NSBlockOperation对象 //任务:block //01 封装操作对象 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1----%@",[NSThread currentThread]); }]; //02 执行操作 [op1 start]; }
- (void)addExecutionBlock:(void (^)(void))block;
注意:
只要NSBlockOperation封装的操作数 > 1
,就会异步执行操作
.NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"3----%@",[NSThread currentThread]); }]; //追加任务 //当一个操作中的任务数量>1的时候,就会开启子线程和当前线程一起执行任务 [op3 addExecutionBlock:^{ NSLog(@"4----%@",[NSThread currentThread]); }]; [op3 addExecutionBlock:^{ NSLog(@"5----%@",[NSThread currentThread]); }]; [op3 start];
2:NSOperationQueue
NSOperationQueue
的作用.NSOperation
可以调用start
方法来执行任务
,但默认是同步执行
的.- 如果将
NSOperation
添加到NSOperationQueue(操作队列)
中,系统会自动异步
执行NSOperation
中的操作.
- 添加操作到NSOperationQueue中
-(void)addOperation:(NSOperation *)op;
-(void)addOperationWithBlock:(void (^)(void))block;
1:操作队列的基本使用(操作 + 队列)
-(void)invocationOperationWithQueue { //01 创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //02 封装操作 NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil]; NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download2) object:nil]; NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download3) object:nil]; //03 把操作添加到队列 [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3]; }
注意:
开启几条子线程并不是由操作的数量决定的
-[queue addOperation:op1];
该方法内部会自动的调用start
方法执行任务
-(void)blockOperationWithQueue { //01 创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //02 封装操作对象 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1----%@",[NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"2----%@",[NSThread currentThread]); }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"3----%@",[NSThread currentThread]); }]; //03 把操作添加到队列中 [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3]; //简便方法:该方法内部首先会把block中的任务封装成一个操作(Operation),然后把该操作直接添加到队列 [queue addOperationWithBlock:^{ NSLog(@"4----%@",[NSThread currentThread]); }]; }
简便方法:
该方法内部首先会把block
中的任务封装成一个操作(Operation)
,然后把该操作直接添加到队列[queue addOperationWithBlock:^{ NSLog(@"4----%@",[NSThread currentThread]); }];
2:最大并发数:同时执行的任务数
最大并发数的相关方法
-(NSInteger)maxConcurrentOperationCount;
-(void)setMaxConcurrentOperationCount:(NSInteger)cnt;
把队列的最大并发数设置为 1 ,就可以达到多个操作在子线程中顺序执行的效果。 -(void)changeSerialQueue { //01 创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //02 封装操作对象 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1----%@",[NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"2----%@",[NSThread currentThread]); }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"3----%@",[NSThread currentThread]); }]; NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"4----%@",[NSThread currentThread]); }]; NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"5----%@",[NSThread currentThread]); }]; //设置最大并发数对于任务数量大于1的操作是无效的 //当操作中任务数量>1的时候,会开启多条子线程和当前线程一起工作 // [op5 addExecutionBlock:^{ // NSLog(@"6----%@",[NSThread currentThread]); // }]; // // [op5 addExecutionBlock:^{ // NSLog(@"7----%@",[NSThread currentThread]); // }]; //设置最大并发数 == 同一时间最多有多少条线程在执行 //maxConcurrentOperationCount == 0 不能执行任务 //NSOperationQueueDefaultMaxConcurrentOperationCount = -1 -1指的是一个最大的值(表示不受限制) queue.maxConcurrentOperationCount = 1; //03 把操作添加到队列中 // [queue addOperation:op1]; // [queue addOperation:op2]; // [queue addOperation:op3]; // [queue addOperation:op4]; // [queue addOperation:op5]; [queue addOperations:@[op1,op2,op3,op4,op5] waitUntilFinished:YES]; NSLog(@"------"); }
注意点:
设置最大并发数对于任务数
量大于1的操作是无效的
创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; 封装操作 NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"4----%@",[NSThread currentThread]); }]; NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"5----%@",[NSThread currentThread]); }]; 追加操作 [op5 addExecutionBlock:^{ NSLog(@"6----%@",[NSThread currentThread]); }]; [op5 addExecutionBlock:^{ NSLog(@"7----%@",[NSThread currentThread]); }]; 设置最大并发数为1 queue.maxConcurrentOperationCount = 1; 将操作添加到队列执行任务 [queue addOperation:op4]; [queue addOperation:op5]; 另一种添加操作到队列的方法 [queue addOperations:@[op4,op5] waitUntilFinished:YES]; NSLog(@"--------"); 这时的执行顺序对于 op5 是无效的
- 注意点:
1.maxConcurrentOperationCount == 0
不能执行任务。
2.默认NSOperationQueueDefaultMaxConcurrentOperationCount = -1
-1指的是一个最大的值(表示不受限制)。
3.最大并发数不一定
等于所开的线程数:所开多少条线程是由系统决定的。
4.[queue addOperations:@[op4,op5] waitUntilFinished:YES];
这是另一种添加操作到队列的方式,后面参数:YES时,必须执行完队列中的操作,才能执行之后的程序;NO时,可以不用执行完队列中的操作就可以执行程序。
4:队列的取消、暂停、恢复
- 取消队列的所有操作
-(void)cancelAllOperations;
- 提示:也可以调用
NSOperation
的- (void)cancel
方法取消单个操作
- 暂停和恢复队列
-(void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列
->-(BOOL)isSuspended;
- (void)start{ //01 创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //02 封装操作 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 5000; ++i) { NSLog(@"1--%zd--%@",i,[NSThread currentThread]); } }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 5000; ++i) { NSLog(@"2--%zd--%@",i,[NSThread currentThread]); } }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 5000; ++i) { NSLog(@"3--%zd--%@",i,[NSThread currentThread]); } }]; NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 5000; ++i) { NSLog(@"4--%zd--%@",i,[NSThread currentThread]); } }]; //设置最大并发数 queue.maxConcurrentOperationCount = 1; //03 添加到队列 [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3]; [queue addOperation:op4]; self.queue = queue; } - (IBAction)suspendBtnClick:(id)sender { //暂停 YES //只能暂停当前操作后面的操作,当前操作不可分割必须执行完毕 //操作是有状态的 [self.queue setSuspended:YES]; } - (IBAction)resumeBtnClick:(id)sender { //恢复 [self.queue setSuspended:NO]; } - (IBAction)cancelBtnClick:(id)sender { //取消 取消所有的操作 //只能取消队列中处理等待状态的操作 [self.queue cancelAllOperations]; }
5:自定义操作、自定义线程
- 自定义
NSOperation
的步骤很简单- 重写
- (void)main
方法,在里面实现想执行的任务
- 重写
- (void)main
方法的注意点- 自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放>池)
- 经常通过
- (BOOL)isCancelled
方法检测操作是否被取消,对取消做出响应
#import "ViewController.h" #import "KXOperation.h" #import "KXGThread.h" @interface ViewController () @property (nonatomic, strong) NSOperationQueue *queue; @end @implementation ViewController -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self test2]; } -(void)thread { //自定义线程 XMGThread *thread = [[XMGThread alloc]init]; [thread start]; } - (IBAction)startBtnClick:(id)sender { // [self test1]; [self test2]; } - (IBAction)suspendBtnClick:(id)sender { //暂停 YES //只能暂停当前操作后面的操作,当前操作不可分割必须执行完毕 //操作是有状态的 [self.queue setSuspended:YES]; } - (IBAction)resumeBtnClick:(id)sender { //恢复 [self.queue setSuspended:NO]; } - (IBAction)cancelBtnClick:(id)sender { //取消 取消所有的操作 //只能取消队列中处理等待状态的操作 [self.queue cancelAllOperations]; } -(void)test2 { //01 创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //02 封装操作(执行任务) XMGOperation *op = [[XMGOperation alloc]init]; //03 把操作添加到队列 [queue addOperation:op]; //内部会调用start方法 ->main //自定义操作的好处:代码复用 self.queue = queue; } @end ------------------------------------------------------------------- #import "KXGOperation.h" @implementation KXGOperation //重写内部的main方法类告诉自定义的操作任务是什么? -(void)main { // NSLog(@"main----%@",[NSThread currentThread]); for (int i = 0; i < 10000; ++i) { NSLog(@"1---%zd--%@",i,[NSThread currentThread]); } //官方建议:在自定义操作的时候每执行完一个耗时操作就判断一下当前操作是否被取消,如果被取消就直接返回 if (self.isCancelled) { return; } NSLog(@"++++++++++++++++"); for (int i = 0; i < 10000; ++i) { NSLog(@"2---%zd--%@",i,[NSThread currentThread]); } if (self.isCancelled) { return; } NSLog(@"++++++++++++++++"); for (int i = 0; i < 10000; ++i) { NSLog(@"3d---%zd--%@",i,[NSThread currentThread]); } } @end ------------------------------------------------------------------- #import "KXGThread.h" @implementation KXGThread -(void)main { NSLog(@"MAIN----%@",[NSThread currentThread]); } @end
6:操作依赖
NSOperation
之间可以设置依赖来保证执行顺序
比如一定要让操作
A
执行完后,才能执行操作B
,可以这么写。[operationB addDependency:operationA];
// 操作B依赖于操作A。
- 可以在不同
queue
的NSOperation
之间创建依赖关系。
注意
:不能相互
依赖
比如:A
依赖B
,B
依赖A
7:操作的监听
- 可以监听一个操作的执行完毕
-(void (^)(void))completionBlock;
-(void)setCompletionBlock:(void (^)(void))block;
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { //01 创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; NSOperationQueue *queue2 = [[NSOperationQueue alloc]init]; //02 封装操作对象 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1----%@",[NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"2----%@",[NSThread currentThread]); }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"3----%@",[NSThread currentThread]); }]; NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"4----%@",[NSThread currentThread]); }]; NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"5----%@",[NSThread currentThread]); }]; //监听任务执行完毕 op4.completionBlock = ^{ NSLog(@"小4任务已完成"); }; [op1 setCompletionBlock:^{ NSLog(@"老幺任务已完成"); }]; //03 设置操作依赖:4->3->2->1->5 //⚠️ 不能设置循环依赖,结果就是两个任务都不会执行 [op5 addDependency:op1]; [op1 addDependency:op2]; //[op2 addDependency:op1]; [op2 addDependency:op3]; [op3 addDependency:op4]; //04 把操作添加到队列 [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3]; [queue addOperation:op4]; [queue2 addOperation:op5]; }
8:线程间的通信
01 创建队列 NSOperationQueue *queue =[[NSOperationQueue alloc]init]; 02封装操作 NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{ 执行任务 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 回到主线程 }]; }]; 03 把操作添加到队列 [queue addOperation:download];
9:GCD和NSOperation的对比:
1)GCD是纯C语言的API,而操作队列则是Object-C的对象。 2)在GCD中,任务用块(block)来表示,而块是个轻量级的数据结构; 相反操作队列中的『操作』NSOperation则是个更加重量级的Object-C对象。 3)具体该使用GCD还是使用NSOperation需要看具体的情况 NSOperation和NSOperationQueue的好处有: 1)NSOperationQueue可以方便的调用cancel方法来取消某个操作,而GCD中的任务是无法被取消的(安排好任务之后就不管了)。 2)NSOperation可以方便的指定操作间的依赖关系。 3)NSOperation可以通过KVO提供对NSOperation对象的精细控制(如监听当前操作是否被取消或是否已经完成等) 4)NSOperation可以方便的指定操作优先级。操作优先级表示此操作与队列中其它操作之间的优先关系,优先级高的操作先执行,优先级低的后执行。 5)通过自定义NSOperation的子类可以实现操作重用.
10:使用Crearte函数创建的并发队列和全局并发队列的主要区别:
1)全局并发队列在整个应用程序中本身是默认存在的并且对应有高优先级、默认优先级、低优先级和后台优先级一共四个并发队列,我们只是选择其中的一个直接拿来用。而Create函数是实打实的从头开始去创建一个队列。 2)在iOS6.0之前,在GCD中凡是使用了带Create和retain的函数在最后都需要做一次release操作。而主队列和全局并发队列不需要我们手动release。当然了,在iOS6.0之后GCD已经被纳入到了ARC的内存管理范畴中,即便是使用retain或者create函数创建的对象也不再需要开发人员手动释放,我们像对待普通OC对象一样对待GCD就OK。 3)在使用栅栏函数的时候,苹果官方明确规定栅栏函数只有在和使用create函数自己的创建的并发队列一起使用的时候才有效(没有给出具体原因) 4)其它区别涉及到XNU内核的系统级线程编程,不一一列举。
注意
引用
9:iOS-底层原理 27:GCD 之 NSThread & GCD & NSOperation
11:iOS开发之多线程:Pthread、NSThread、GCD、NSOperation、NSOperationQueue