OS X Snow Leopard 和 iOS 开始,apple公司为开发者引入了新的多线程编程功能"Grand Central Dispatch"(超级派发中心,也称大调度中心)。
1. 概要
1.1 什么是GCD
GCD是异步执行任务的一种技术。开发者只需要定义需要执行的任务并将任务提交到适当的Dispatch Queue中,GCD就能生成必要的线程并有计划的执行任务。
dispatch_async(queue, ^{
/*
* 长时间处理
* 例如:数据库访问
**/
/*
* 长时间处理结束,主线程使用该处理结果
**/
dispatch_async(dispatch_get_main_queue(), ^{
/*
* 回到主线程进行处理
* 如:根据数据更新用户界面
**/
});
});
上面的任务是GCD一种简单实现,定义了一个block任务(长时间数据库处理任务),然后将该任务异步提交到队列queue中,GCD根据队列中的任务生成必要的线程,来执行长时间数据库处理任务,并提交新任务到主队列(回到主线程)进行UI刷新工作。
思考:还有没有其他方式从子线程回到主线程?哪种方式好?
1.2 多线程编程
先来看几个关键概念
线程:一条CPU指令连续不阶段的执行流就是线程。
多线程:多核CPU可以并列的执行多条cpu指令流。
原生核CPU:真正意义上的多核CPU,每个核心相互独立,拥有自己的总线。
封装核CPU:简单的封装多个单核CPU,只有一条共用总线。
目前为止,iphone 6s是最新的iOS平台设备,采用的是原生双核A9处理器。
多线程编程存在问题:
- 1.资源竞争:多个线程更新相同资源导致数据不一致
- 2.死锁:停止等待事件的线程会导致多个线程相互持续等待
- 3.内存消耗:使用太多线程会消耗内存
后面给出以上问题解决方案。
多线程应用场景:
主线程主要用来处理界面刷新,屏幕点击事件等操作,长时间的处理不要放在主线程中执行,应该放在其他线程中执行。
2.GCD 主要 API
2.1 Dispatch Queue
开发者要做的只是在block中定义想要执行的任务并提交到适当的Dispatch Queue中,Dispatch Queue是执行处理的等待队列,负责控制任务进出线程的次序,执行处理时遵循先进先出(FIFO)原则,等待队列分为Serial Dispatch Queue (同步等待队列) 和 Concurrent Dispatch Queue (异步等待队列)。
表 2.1-1 Dispatch Queue种类
Dispatch Queue | 说明 |
---|---|
Serial Dispatch Queue | 等待现在执行中处理结束 |
Concurrent Dispatch Queue | 不等待现在执行中处理结束 |
示例:
dispatch_async(queue, blk0);
dispatch_async(queue, blk1);
dispatch_async(queue, blk2);
dispatch_async(queue, blk3);
dispatch_async(queue, blk4);
dispatch_async(queue, blk5);
dispatch_async(queue, blk6);
dispatch_async(queue, blk7);
- 当 queue 是 Serial Dispatch Queue 时,执行顺序是:blk0->blk1->blk2->blk3->blk4->blk5->blk6->blk7
- 当 queue 是 Concurrent Distach Queue 时,执行顺序如下表
表2.1-2 Concurrent Dispatch Queue 执行示例
线程0 | 线程1 | 线程2 | 线程3 |
---|---|---|---|
blk0 | blk1 | blk2 | blk3 |
blk4 | blk6 | blk5 | |
blk7 | . |
由上可以发现
- GCD会为同步等待队列开辟一条线程,同步等待队列里地任务是按照FIFO的原则在该条线程中逐个处理;
- GCD会为异步等待队列开辟多条线程,异步等待队列里的任务是会并行的在多个线程中进行处理。
本例中,GCD为异步等待队列开辟了四条线程,所以一次能并发的执行blk0-blk3四条任务,剩余任务会遵循FIFO原则,等待前面的任务执行完毕,有空闲线程,再利用该空闲线程执行。具体的可创建线程数和最大并发线程数有关,属于系统管理。
应用场景:
- 同步等待队列serial dispatch queue适用于需要按顺序执行的任务;
- 异步等待队列Concurrent dispatch queue适用于不需要按顺序执行,执行效率高的场景;
2.2 创建Dispatch Queue
获取队列有两种方式,手动创建和直接获取
手动创建
使用 dispatch_queue_create 函数生成
//创建同步等待队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.myqueue", DISPATCH_QUEUE_SERIAL);
//创建异步等待队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.myqueue", DISPATCH_QUEUE_CONCURRENT);
函数中第一个参数使用反向域名的方式标记队列,可以设为NULL,为了获取更多调试信息建议设置;第二个参数选择使用serial或Concurrent队列。
IMPORTANT
If your app is built with a deployment target of OS X v10.8 and later or iOS v6.0 and later, dispatch queues are typically managed by ARC, so you do not need to retain or release the dispatch queues.
For compatibility with existing code, this behavior is configurable. See Dispatch Queues and Automatic Reference Counting for details.
翻译:如果你的iOS app版本大于6.0,不需要手动管理手动创建的dispatch对象
�直接获取
使用系统标准提供的Dispatch Queue
//主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//系统全局并发队列
dispatch_queue_t golbalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
主队列是serial dispatch queue,系统全局队列是Concurrent dispatch queue,很多时候我们没必要手动创建异步队列,直接使用系统的就够了。
2.3 dispatch_set_target_queue
设置使用dispatch_queue_create函数手动创建生成队列的优先级
//创建一个队列
dispatch_queue_t myqueue = dispatch_queue_create("com.example.myqueue", DISPATCH_QUEUE_CONCURRENT);
//创建目标队列
dispatch_queue_t targetQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//myqueue 使用 targetQueue的优先级策略
dispatch_set_target_queue(myqueue, targetQueue);
应用场景:
- 需要变更执行优先级的情况
- 需要防止并行执行的场景,可以将serial queue设为并发队列的参考。
2.4 dispatch_after
定时处理
//设置定时器为3秒
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
//3秒后提交任务到主队列
dispatch_after(time, dispatch_get_main_queue(), ^{
//操作任务
});
注意:3秒后提交不等于3秒后执行,具体执行时间和当前正在处理任务数以及RunLoop周期有关
应用场景:
- 需要延时提交的情况
2.5 Dispatch Group
分组管理任务,能监测整个分组中的任务执行状态。
//获取队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//创建分组
dispatch_group_t group = dispatch_group_create();
//异步提交block任务到等待队列并分组
dispatch_group_async(group, queue, ^{NSLog(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});
/*当上面提交到group中的block全部执行完毕,将完成操作的block提交到queue中等待执行*/
dispatch_group_notify(group, queue, ^{
//完成操作
NSLog(@"完成");
});
执行结果:
blk1 -> blk2 -> blk0 ->完成
因为queue是Concurrent queue 所以多个线程并行执行,执行完毕后再执行"完成"
这里还有一个函数值得注意,dispatch_group_wait函数,该函数也可以等待group分组任务完成后做一些特殊操作,会在指定时间内等待分组任务执行完毕,然后进入下一行代码;如果指定时间内分组内任务未执行完毕,该函数返回1,否则返回0.
//一直等待直到完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//等待3秒时间
long result = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC));
if (result == 0) {
/*
* group分组内的任务全都执行完毕
*/
} else {
/*
* group分组内某个任务还在执行中
*/
}
应用场景:
- 在提交到队列中的所有任务执行完毕后做结束处理
- 提交任务到主队列,将主队列和一个group关联,可以监测任务是否都执行完毕,不耗费多余等待时间。
dispatch_group_enter
指示一个block任务已经进入group状态
dispatch_group_leave
指示一个block任务执行完毕,可以退出组管理
以上两个方法,类似于任务计数器,协同group管理自己的任务,当没有任务,即任务计数为0时调用dispatch_group_notify函数
2.6 dispatch_barrier_async
这里申明一个概念**dispatch_async **和 dispatch_sync区别
- dispatch_sync:提交任务到队列,并等待block中任务执行完毕进入下一步
- dispatch_async:提交任务到队列,不等待block中任务执行完毕即可进入下一步。
dispatch_async(queue, ^{NSLog(@"blk0_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk1_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk2_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk3_for_reading");});
dispatch_barrier_async(queue, ^{
//blk4 writing任务
//添加blk4到队列中,blk4前面提交的block任务执行完毕后,blk4后面提教的block任务才能执行
});
dispatch_barrier_sync(queue, ^{
//blk4 writing任务
/*如果使用这个函数
添加blk4到队列中,并等待blk4前面的任务执行完毕,执行blk4,然后进入下一步继续提交任务。
*/
});
dispatch_async(queue, ^{NSLog(@"blk5_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk6_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk7_for_reading");});
上面的代码中,dispatch_barrier_async 和 dispatch_barrier_sync都起到了隔离blk0-blk3 和 blk5-blk6执行顺序功能,不同的是dispatch_barrier_async这一步代码不会阻塞,会继续向下执行提交操作,dispatch_barrier_sync会阻塞代码,停止提交,等待执行完毕载提交。
2.7 dispatch_apply
该函数按指定的次数将指定的Block提交到指定的dispatch queue中,并等待全部执行完毕。当queue是Concurrent dispatch queue时,所有提交的block可以异步的执行,但是要注意安全操作问题。
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu", index);
});
可以放入一个全局队列,异步执行。
2.8 dispatch_suspend/dispatch_resume
暂停队列 和 恢复队列
dispatch_suspend(queue);//暂停
dispatch_resume(queue);//恢复
2.9 dispatch semaphore
出现数据竞争的时候,可以进行排他控制,使用信号机制,0代表等待返回,阻塞代码,直到信号>=1
生成计数信号
dispatch_semaphore_t semphore = dispatch_semphore_create(1);
使用wait函数判断计数信号数值是否>=1,是则-1并返回;否则阻塞代码等待,直到某处将信号值设置为>=1.
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
将计数值+1
dispatch_semaphore_signal(semaphore)
应用场景:
- 资源竞争,控制先后顺序
3.0 dispatch_once
保证某个操作只执行一次
static dispatch_once_t pred;
dispatch_once(&pred,^{
//do something
});
3.1 dispatch I/O
使用多个线程将数据分段更快的读取数据。
现在我们回到一开始提出三个使用多线程的问题,资源竞争、死锁、内存消耗
参考:
GCD官方文档
《Objective-c 高级编程》