GCD初识一
谈到iOS多线程,一般都会谈到四种方式:pthread、NSThread、GCD和NSOperation。其中,苹果推荐也是我们最经常使用的无疑是GCD。所以掌握gcd的用法也是每个ios开发者必备的技能
GCD是一套低层级的C API,通过 GCD,开发者只需要向队列中添加一段代码块(block或C函数指针),而不需要直接和线程打交道。GCD在后端管理着一个线程池,它不仅决定着你的代码块将在哪个线程被执行,还根据可用的系统资源对这些线程进行管理。
特点:
- 快,更快的内存效率,因为线程栈不暂存于应用内存
- 稳,提供了自动的和全面的线程池管理机制,稳定而便捷。
- 准,提供了直接并且简单的调用接口,使用方便,准确。
队列和任务
学习gcd,肯定会接触到各种词汇,队列和任务
队列
调度队列是一个对象,它会以first-in、first-out的方式管理您提交的任务。GCD有三种队列类型:
- 串行队列,让任务一个接着一个地执行(一个任务执行完后,再执行下一个任务),GCD中获得串行有2种途径(使用dispatch_queue_create函数,并指定队列类型DISPATCH_QUEUE_SERIAL,主队列),放在主队列中的任务,都会放到主线程中执行
- 并行队列,可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务),并发队列会基于系统负载来合适地选择并发执行这些任务;并发功能只有在异步(dispatch_async)函数下才有效,因为异步函数才具备开启新线程的能力,而同步函数只能在当前线程中执行不具备开启线程的能力;并发队列获取2种途径 (手动创建并发队列,dispatch_queue_create,并指定队列类型DISPATCH_QUEUE_CONCURRENT;GCD默认已经提供了全局的并发队列Global queue)
- 主队列,与主线程功能相同;主线程是唯一可用于更新 UI 的线程。提交至main queue的任务会在主线程中执行。main queue可以调用dispatch_get_main_queue()来获得。因为main queue是与主线程相关的,所以这是一个串行队列。和其它串行队列一样,这个队列中的任务一次只能执行一个。
任务
GCD中的任务只是一个代码块,它可以指一个block或者函数指针。根据这个代码块添加进入队列的方式,将任务分为同步任务和异步任务:
- 同步任务,使用dispatch_sync将任务加入队列。将同步任务加入串行队列,会顺序执行,一般不这样做并且在一个任务未结束时调起其它同步任务会死锁。将同步任务加入并行队列,会顺序执行,但是也没什么意义。
GCD常见用法及场景展示
1. dispatch_async
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
// 一个异步的任务,例如网络请求,耗时的文件操作等等
...
dispatch_async(dispatch_get_main_queue(), ^{
// UI刷新
...
});
});
这种用法非常常见,比如开启一个异步的网络请求,待数据返回后返回主队列刷新UI;又比如请求图片,待图片返回刷新UI等等。
2. dispatch_after
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
// 在queue里面延迟执行的一段代码
...
});
这为我们提供了一个简单的延迟执行的方式,比如在view加载结束延迟执行一个动画等等。
3. dispatch_once
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行一次的任务
...
});
可以使用其创建一个单例,也可以做一些其他只执行一次的代码
4. dispatch_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, ^{
// 异步任务1
});
dispatch_group_async(group, queue, ^{
// 异步任务2
});
dispatch_group_notify(group, mainQueue, ^{
// 任务完成后,在主队列中做一些操作
...
});
模拟网络请求的代码:
dispatch_group_enter(group);
[JDApiService getActivityDetailWithActivityId:self.activityId Location:stockAddressId SuccessBlock:^(NSDictionary *userInfo) {
// 数据返回后一些处理
...
dispatch_group_leave(group);
} FailureBlock:^(NSError *error) {
// 数据返回后一些处理
...
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[JDApiService getAllCommentWithActivityId:self.activityId PageSize:3 PageNum:self.commentCurrentPage SuccessBlock:^(NSDictionary *userInfo) {
// 数据返回后一些处理
...
dispatch_group_leave(group);
} FailureBlock:^(NSError *error) {
// 数据返回后一些处理
...
dispatch_group_leave(group);
}];
执行dispatch_group_notify的任务。
dispatch_group_notify(group, mainQueue, ^{
// 一般为回主队列刷新UI
...
});
5. dispatch_barrier_async
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 任务1
...
});
dispatch_async(queue, ^{
// 任务2
...
});
dispatch_async(queue, ^{
// 任务3
...
});
dispatch_barrier_async(queue, ^{
// 任务4
...
});
dispatch_async(queue, ^{
// 任务5
...
});
dispatch_async(queue, ^{
// 任务6
...
});
6. dispatch_apply
// for循环做一些事情,输出0123456789
for (int i = 0; i < 10; i ++) {
NSLog(@"%d", i);
}
// dispatch_apply替换(当且仅当处理顺序对处理结果无影响环境),输出顺序不定,比如1098673452
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*! dispatch_apply函数说明
*
* @brief dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API
* 该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等到全部的处理执行结束
*
* @param 10 指定重复次数 指定10次
* @param queue 追加对象的Dispatch Queue
* @param index 带有参数的Block, index的作用是为了按执行的顺序区分各个Block
*
*/
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu", index);
});
那么,dispatch_apply有什么用呢,因为dispatch_apply并行的运行机制,效率一般快于for循环的类串行机制(在for一次循环中的处理任务很多时差距比较大)。比如这可以用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性,如果用for循环,耗时较多,并且每个表单的数据没有依赖关系,所以用dispatch_apply比较好。
7. dispatch_suspend和dispatch_resume
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_suspend(queue); //暂停队列queue
dispatch_resume(queue); //恢复队列queue
这两个函数不会影响到队列中已经执行的任务,队列暂停后,已经添加到队列中但还没有执行的任务不会执行,直到队列被恢复。
8. dispatch_semaphore_signal
// a、同步问题:输出肯定为1、2、3。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(1);
dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);
dispatch_semaphore_t semaphore3 = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
// 任务1
dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
NSLog(@"1\n");
dispatch_semaphore_signal(semaphore2);
dispatch_semaphore_signal(semaphore1);
});
dispatch_async(queue, ^{
// 任务2
dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
NSLog(@"2\n");
dispatch_semaphore_signal(semaphore3);
dispatch_semaphore_signal(semaphore2);
});
dispatch_async(queue, ^{
// 任务3
dispatch_semaphore_wait(semaphore3, DISPATCH_TIME_FOREVER);
NSLog(@"3\n");
dispatch_semaphore_signal(semaphore3);
});
// b、有限资源访问问题:for循环看似能创建100个异步任务,实质由于信号限制,最多创建10个异步任务。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
for (int i = 0; i < 100; i ++) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
// 任务
...
dispatch_semaphore_signal(semaphore);
});
}
主要是有两种用法: 1.解决同步问题 2.解决有限资源访问
内存和安全
- MRC:用dispatch_retain和dispatch_release管理dispatch_object_t内存。
- ARC:ARC在编译时刻自动管理dispatch_object_t内存,使用retain和release会报错。
- dispatch_queue是线程安全的,你可以随意往里面添加任务。
一些经常拿出来说的代码
- 在串行队列里的异步任务,任务里再同一个队列里异步任务
这里执行的结果肯定是:1,3,2 且线程是同一个 因为是串行队列;dispatch_queue_t queue = dispatch_queue_create("com.tzjy.-02--GCD", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{//block1 NSLog(@"1"); NSLog(@"1---%@",[NSThread currentThread]); dispatch_async(queue, ^{//block2 NSLog(@"2"); NSLog(@"2---%@",[NSThread currentThread]); }); sleep(2); NSLog(@"3"); NSLog(@"3---%@",[NSThread currentThread]); });
- 在并发队列里的异步任务,任务里再同一个队列里异步任务
这里的执行结果是1,2,3 且会有不同的两个线程,因为是并发队列;dispatch_queue_t globalqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{//block1 NSLog(@"1"); NSLog(@"1---%@",[NSThread currentThread]); dispatch_async(queue, ^{//block2 NSLog(@"2"); NSLog(@"2---%@",[NSThread currentThread]); }); sleep(2); NSLog(@"3"); NSLog(@"3---%@",[NSThread currentThread]); });
-
在串行队列里同,异步任务再同步任务,会发生死锁
dispatch_queue_t queue = dispatch_queue_create("com.tzjy.-02--GCD", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{//block1 NSLog(@"1"); dispatch_sync(queue, ^{//block2 NSLog(@"2"); }); sleep(2); NSLog(@"3"); });
这里会崩溃掉,因为任务一会等待任务二,而任务二又会等待任务一,这样形成死锁;如果这里的队列不是串行队列,是并行队列,则不会死锁。
如果使用dispatch_sync,如果执行dispatch_sync 这句的上下文环境的“队列”,跟 dispatch_sync(queue, ^{ 的这个queue 是同一个队列,不用想了绝逼死锁。。。当然还有一个条件,这个队列必须是单行车道,也就是串行队列,如果是并行的,不会有死锁,为什么呢?
假如queue是个并行队列,当执行到dispatch_sync 的时候,dispatch_sync无需等待当前任务执行完毕,此时它就会执行,但是会阻塞当前线程,只有等它执行完了,下面的任务才会接着去执行了。
网友评论