1、GCD概念
GCD特点优势
- 全称
Grand Central Dispatch
- 纯C语言,函数强大
- 苹果为多核并行运算提出的解决方案
- 会利用更多的CPU内核(比如双核、四核)
自动管理线程生命周期
(创建线程、调度任务、销毁线程),程序员只需告诉GCD执行什么任务,不需编写任何线程管理代码
2、同步、异步
- 任务使用block封装
- 任务的block没有参数也没有返回值
- 同步函数和异步函数都是耗时任务,执行起来
会产生时间差
- 可以方便理解为当前任务的单线程和多线程
2.1、同步函数:dispatch_sync
- 必须等当前语句执行完毕,才能执行下一条语句
- 不会开启线程
- 在当前执行block任务
2.2、异步函数:dispatch_async
- 不用等待当前语句执行完毕,就可以执行下一条语句
- 会开启线程执行block的任务
- 异步是
多线程的代名词
dispatch_queue_t queue = dispatch_queue_create("LZ",DISPATCH_QUEUE_CONCRRENT);
NSLog(@"1");
dispatch_async(queue, ^{ //同步、异步函数都是耗时操作,所以先打印5(针对新创建,未被其他因素影响的线程)
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
}); //同步函数阻塞后续内容执行,所以先3后4
NSLog(@"4");
});
NSLog(@"5");
执行结果:15234
2.3、死锁(_dispatch_sync_f_slow)
将上边的代码从并发改为串行
//为方便讲解,我们把代码标上序号
1. dispatch_queue_t queue = dispatch_queue_create("LZ",DISPATCH_QUEUE_SERIAL);
2. NSLog(@"1");
3. dispatch_async(queue, ^{ //同步、异步函数都是耗时操作
4. NSLog(@"2");
5. dispatch_sync(queue, ^{ //队列顺序:1、5、2、同步block块、4、3,但是同步block护犊子强制3先执行,在同步函数不能开辟新线程的情况下,遵循FIFO,3只能在4后执行,3无法插队造成死锁
6. NSLog(@"3");
7. });
8. NSLog(@"4");
9. }); <--- //上面同步函数阻塞的不止是4的打印,还有异步函数的完成,异步函数内部已结束,要先执行完成,再执行3,但被同步函数阻塞,因此将4的打印屏蔽也会死锁
10. NSLog(@"5");
执行结果:152 崩溃
2.4、死锁修改
1. dispatch_queue_t queue = dispatch_queue_create("LZ",DISPATCH_QUEUE_SERIAL);
2. NSLog(@"1");
3. dispatch_async(queue, ^{
4. NSLog(@"2");
//从 sync 改为 async
5. dispatch_async(queue, ^{
6. NSLog(@"3");
7. });
//睡眠2秒
8. sleep(2);
9. NSLog(@"4");
10. });
11. NSLog(@"5");
执行结果:152 (等待2秒) 43
-
3 并没有比 4 先打印,因为 异步只是任务从队列中取出来后放到另一个线程里执行,第9行任务从队列里出来前睡了2秒,按照串行队列规则,第9行任务不从队列中出去第5-7行任务从队列里出不来,线程拿不到任务执行
-
那么将 NSLog(@"4") 也放入异步执行的结果呢?
1. dispatch_queue_t queue = dispatch_queue_create("LZ",DISPATCH_QUEUE_SERIAL); 2. NSLog(@"1"); 3. dispatch_async(queue, ^{ 4. NSLog(@"2"); // 都改为 async 5. dispatch_async(queue, ^{ 6. sleep(2); 7. NSLog(@"3"); 8. }); 9. dispatch_async(queue, ^{ 10. NSLog(@"4"); 11. }); 12. }); 13. NSLog(@"5"); 执行结果:152 (等待2秒) 34
- 4 的打印要等待 3 睡眠2秒后才执行
- 虽然打印 3 和 4 都是异步执行的,但因为
串行队列只能开启一条线程
,他们异步后的执行线程是同一条,因此表面上看虽然都是异步了,但 4 还是在 3 后执行
补充
- 异步和多线程并不是同等关系,
异步是目的
,多线程是异步的一种手段。异步是当一个调用者,调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其他事情的能力
,实现异步可以采用多线程技术或者交给其他进程来处理(APP间进程独立、MAC系统不独立,文中主要讨论APP,故将异步与多线程近似对待)等
2、队列
-
队列是一种调度,将任务排序并进入
runnable
状态,具体执行还是在线程中,需要线程池 -
串行队列:数据结构:FIFO(先IN先OUT),先调度先执行
-
并发队列(
Concurrent
):同一时间可以调度多个任务,先调度不一定先执行
2.1、并发与并行
- 并行(
Parallel
):两个或两个以上事件或活动在同一时刻发生,线程可在不同CPU上同时执行(hadoop集群),多个线程互相不抢夺资源,可同时进行 - 并行和并发:并行是彻底的同一时间执行多任务,并发是将任务打散,微观上以很快的速度交替运行,宏观上看实现了同时执行
- 异步是多个任务并行的前提条件
2.2、函数与队列
- 同步、异步: 指能否开启新线程的能力
- 串行、并发: 指是否需要等待上一个任务完成才能调度下一个任务
注意
-
异步与并发: 虽然异步和并发都是说不用等这个任务执行完,就可以执行后边的任务,但是异步是说将这个任务放到别的线程执行,而并发指的是还是这个线程,只是让后边任务先执行
-
类比:
- 异步:去另一台提款机取钱
- 并发:前边的忘密码了后边先取
-
同步 + 串行队列
:不能开启新线程;在一条线程中,完成一个任务后才能从队列中拿一个新任务执行 -
同步 + 并发队列
:不能开启新线程;在一条线程中,如果前边任务停滞则从队列中拿下一个任务执行(可能一口气从队列中拿好几个任务执行),执行哪个看前置任务停滞后的系统调度 -
异步 + 串行队列
:虽然能开启新线程,但 只能开启一条线程;线程完成一个任务后才能从队列中拿一个新任务执行 -
异步 + 并发队列
:能开启新线程,而且 能开一堆线程;线程们每条都有一下拿好几个任务执行的能力,执行哪个看前置任务停滞后的系统调度
小测试1
1. dispatch_queue_t queue = dispatch_queue_create("LZ",DISPATCH_QUEUE_SERIAL);
2. dispatch_queue_t queue2 = dispatch_queue_create("LZ2",DISPATCH_QUEUE_SERIAL);
3. dispatch_queue_t queue3 = dispatch_queue_create("LZ3",DISPATCH_QUEUE_CONCURRENT);
4.
5. NSLog(@"1");
6. dispatch_async(queue, ^{ //可尝试改为queue3
7. sleep(2);
8. NSLog(@"2");
9. dispatch_async(queue, ^{ //可尝试改为queue3
10. NSLog(@"3");
11. });
12. NSLog(@"4");
13. });
14. NSLog(@"5");
15.
16. dispatch_async(queue, ^{ //可尝试改为queue3
17. NSLog(@"6");
18. });
执行结果:15 (睡眠2秒) 2463
-
同步、异步向队列中添加任务都是将代码块(7-12行、17行、10行)添加到队列中,
代码块中内容先不用理会,只有线程执行到时才开始考虑
,因此队列中任务的顺序是 5行 --> 14行 --> 7-12行 --> 17行 --> 8行 --> 10行,本例中因为放到了 串行队列 中,因此17行虽然是异步,也要等7行睡眠2秒后才能执行 -
如果将 串行队列queue 换成 并发队列queue3,则执行结果:156 (睡眠2秒) 243,因为 并发队列前置任务停滞能直接取下一个任务执行,7-12行任务睡眠了就调度17行任务先执行
小测试2
1. dispatch_queue_t queue = dispatch_queue_create("LZ",DISPATCH_QUEUE_SERIAL);
2. dispatch_queue_t queue2 = dispatch_queue_create("LZ2",DISPATCH_QUEUE_SERIAL);
3. dispatch_queue_t queue3 = dispatch_queue_create("LZ3",DISPATCH_QUEUE_CONCURRENT);
4.
5. NSLog(@"1");
6. dispatch_async(queue, ^{
7. NSLog(@"2");
8. dispatch_async(queue, ^{
9. NSLog(@"3");
10. });
11. NSLog(@"4");
12. });
13. NSLog(@"5");
14.
//改为queue2
15. dispatch_async(queue2, ^{
16. NSLog(@"6");
17. });
执行结果:15 (6?) 2 (6?) 4 (6?) 3 (6?)
- 最后的16行任务与其他任务 被添加到了不同的队列中,所以说与它们根本就没有什么关系,因此 数字6 的打印可能在打印数字2、4、3时的任何一个时机执行
2.3、主队列与全局队列
2.3.1、主队列dispatch_get_main_queue()
- 专门用来在主线程上调度任务的
串行队列
,不会开启线程 - 如果
当前主线程正在有任务执行
,那么无论主队列中当前被添加了什么任务,都不会被调度
2.3.2、全局并发队列dispatch_get_global_queue(0,0)
- 全局队列是一个
并发队列
- 在使用多线程时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列
3、GCD的应用
- 单例
- 同步/异步
- 延迟
- dispatchSource
- 调度组
- 线程通讯
- 栅栏函数
- 信号量/锁
阻塞方式
栅栏函数
- 作用:控制任务执行顺序,同步
- dispatch_barrier_async:前边的任务执行完毕后才会来到这里
- dispatch_barrier_sync :作用相同,但是会阻塞线程,影响后边的任务执行
- 只能控制
同一并发队列
调度组
- dispatch_gruop_create:创建组
- dispatch_group_async :进组任务
- dispatch_group_notify :进组任务执行完毕通知
- dispatch_group_wait :进组任务等待
- dispatch_group_enter :进组
- dispatch_group_leave :出组
信号量dispatch_semaphore_t
- dispatch_semaphore_create:创建信号量
- dispatch_semaphore_wait : 信号量等待
- dispatch_semaphore_signal :信号量释放
- 同步-->当锁,
控制GCD最大并发数
Dispatch_Source
- 其CPU负荷非常小,尽量不占用资源
- 联结的优势
- 在任一线程上调用它的一个函数
dispatch_source_merge_data
后,会执行Dispatch Source事先定义好的句柄(可以把这个句柄简单理解为一个block)这个过程叫Custom event
,用户事件。是Dispatch Source支持处理的一种事件- 句柄是一种指向指针的指针,它指向的就是一个类或者结构,它和系统有很密切的关系
HINSTANCE
(实例句柄),HBITMAP
(位图句柄),HDC
(设备表述句柄),HICON
(图标句柄)等。这当中还有一个通用句柄,就是HANDLE
- 句柄是一种指向指针的指针,它指向的就是一个类或者结构,它和系统有很密切的关系
- dispatch_source_create: 创建源
- dispatch_source_set_event_handler:设置源事件回调
- dispatch_source_merge_data: 源事件设置数据
- dispatch_source_get_data: 获取源事件数据
- dispatch_resume: 继续
- dispatch_suspend: 挂起