GCD
在项目中 偶尔 用到 GCD , 整理了一番,GCD 的 基本使用都囊括了。
什么是 GCD ?
在这里 贴一下苹果《并发编程指南》中的介绍:
Grand Central Dispatch(GCD):系统管理线程,你不需要编写线程代码。只需定义想要执行的任务,然后添加到适当的 dispatchqueue。GCD 会负责创建线程和调度你的任务。系统直接提供线程管理,比应用实现更加高效
优点:
l 直观而简单的编程接口
l 提供自动和整体的线程池管理
l 提供汇编级调优的速度
l 更加高效地使用内存
l 不会 trap 内核 underload
l 异步分派任务到 dispatch queue 不会导致 queue 死锁 l伸缩性强
l serial dispatch queue 比锁和其它同步原语更加高效
总体上来说,GCD 技术 让我们 可以 轻而易举地 写 出 非常高效 的多线程的 代码,并比大多数情况下 手动 写的 管理 多线程的 代码还要高效。
- 什么是 GCD ? 一种 系统管理线程 的技术,不需要 编写 线程 代码,只需定义要执行的任务 block。然后添加到 适当的 dispatch queue 中。系统提供线程管理,比自行实现更加高效。
- 什么 是任务 block ? 要执行的 代码 块。
- 什么是 dispatch queue ?执行任务的机制,分为 serial 和 concurrent 两种。注意队列都是先进先出的。
- 给个例子?
-(void)simpleGCD{
NSLog(@"%s begin",__PRETTY_FUNCTION__);
dispatch_queue_t myqueue = dispatch_queue_create("com.boyaa.texas.database",DISPATCH_QUEUE_CONCURRENT );//DISPATCH_QUEUE_SERIAL (or NULL)
dispatch_async(myqueue, ^{
[self heavyTask1];
});
NSLog(@"%s end",__PRETTY_FUNCTION__);
}
-(void)heavyTask1{
NSLog(@"%s begin",__PRETTY_FUNCTION__);
[NSThread sleepForTimeInterval:1];
NSLog(@"%s end",__PRETTY_FUNCTION__);
}
在 simpleGCD 中,使用 dispatch_queue_create 创建 一个 myQueue , 然后 使用 dispatch_async 将 一个 block 放到 这个 myQueue 中 执行 。
执行 顺序 如下:
ConcurrentProgramingExample[2437:60b]-[XxpViewController simpleGCD] begin
2014-06-30 00:26:37.351ConcurrentProgramingExample[2437:60b] -[XxpViewController simpleGCD] end
2014-06-30 00:26:37.351ConcurrentProgramingExample[2437:1303] -[XxpViewController heavyTask1] begin
2014-06-30 00:26:38.353ConcurrentProgramingExample[2437:1303] -[XxpViewController heavyTask1] end
开始
1. serial queue 串行队列,在队列里面的任务 按照 FIFO 的顺序,执行完一个任务才开始 下一个 任务
-(void)serialGCD1{
NSLog(@"%s begin",__PRETTY_FUNCTION__);
dispatch_queue_t myqueue = dispatch_queue_create("com.boyaa.texas.database",DISPATCH_QUEUE_SERIAL );//DISPATCH_QUEUE_SERIAL (or NULL)
dispatch_async(myqueue, ^{
[self heavyTask3];
});
dispatch_async(myqueue, ^{
[self heavyTask1];
});
dispatch_async(myqueue, ^{
[self heavyTask2];
});
NSLog(@"%s end",__PRETTY_FUNCTION__);
}
定义了 三个 任务 ,休眠时间如名称后缀。按顺序加入到队列中。观察 开始 和结束时间
2014-06-30 00:37:54.231ConcurrentProgramingExample[2571:60b] -[XxpViewController serialGCD1] begin
2014-06-30 00:37:54.232ConcurrentProgramingExample[2571:1303] -[XxpViewController heavyTask3] begin
2014-06-30 00:37:54.232ConcurrentProgramingExample[2571:60b] -[XxpViewController serialGCD1] end
2014-06-30 00:37:57.233ConcurrentProgramingExample[2571:1303] -[XxpViewController heavyTask3] end
2014-06-30 00:37:57.234ConcurrentProgramingExample[2571:1303] -[XxpViewController heavyTask1] begin
2014-06-30 00:37:58.236ConcurrentProgramingExample[2571:1303] -[XxpViewController heavyTask1] end
2014-06-30 00:37:58.236ConcurrentProgramingExample[2571:1303] -[XxpViewController heavyTask2] begin
2014-06-30 00:38:00.238ConcurrentProgramingExample[2571:1303] -[XxpViewController heavyTask2] end
2. concurrent queue 并发队列,按FIFO 的顺序执行任务 。但执行完成时间 是 不定的。
-(void)concurrentGCD{
NSLog(@"%s begin",__PRETTY_FUNCTION__);
dispatch_queue_t myqueue = dispatch_queue_create("com.boyaa.texas.database",DISPATCH_QUEUE_CONCURRENT );
dispatch_async(myqueue, ^{
[self heavyTask3];
});
dispatch_async(myqueue, ^{
[self heavyTask1];
});
dispatch_async(myqueue, ^{
[self heavyTask2];
});
NSLog(@"%s end",__PRETTY_FUNCTION__);
}
创建 并发队列 (使用DISPATCH_QUEUE_CONCURRENT),然后使用 异步派发 把 任务加入到队列中。注意 任务的开始时间 和结束 时间。
2014-06-30 00:43:00.116ConcurrentProgramingExample[2620:60b] -[XxpViewController concurrentGCD] begin
2014-06-30 00:43:00.117ConcurrentProgramingExample[2620:60b] -[XxpViewController concurrentGCD] end
2014-06-30 00:43:00.117ConcurrentProgramingExample[2620:1303] -[XxpViewController heavyTask3] begin
2014-06-30 00:43:00.117ConcurrentProgramingExample[2620:3a03] -[XxpViewController heavyTask2] begin
2014-06-30 00:43:00.117ConcurrentProgramingExample[2620:3903] -[XxpViewController heavyTask1] begin
2014-06-30 00:43:01.129ConcurrentProgramingExample[2620:3903] -[XxpViewController heavyTask1] end
2014-06-30 00:43:02.129ConcurrentProgramingExample[2620:3a03] -[XxpViewController heavyTask2] end
2014-06-30 00:43:03.129ConcurrentProgramingExample[2620:1303] -[XxpViewController heavyTask3] end
- 系统提供了三个 并发 dispatch_queue 。有三个 优先级
// 所有 高级别 线程启动 后,才会启动低级别 线程
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
Eg :
dispatch_queue_t myqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
当要 需要 并发 队列 时 ,应该 使用 系统提供的 全局 并发队列。不建议自行 创建。创建线程需要开销。
/*
@constant DISPATCH_QUEUE_PRIORITY_BACKGROUND
* Items dispatched to the queue will run at backgroundpriority, i.e. the queue
* will be scheduled for execution after all higherpriority queues have been
* scheduled and the system will run items on this queue ona thread with
* background status as per setpriority(2) (i.e. disk I/Ois throttled and the
* thread's scheduling priority is set to lowest value).
*/
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
- 获取主线程
UI 更新代码 需要 在主线程 上运行。
dispatch_queue_tmyqueue = dispatch_get_main_queue();
- 添加任务到 Queue
dispatch_async : 异步地把任务加入到 队列中 ,并向下执行。
dispatch_sync : 同步添加任务 到 队列中,执行完任务才向下执行。
-(void)serialGCD2{
NSLog(@"%s begin",__PRETTY_FUNCTION__);
dispatch_queue_t myqueue = dispatch_queue_create("com.boyaa.texas.database",DISPATCH_QUEUE_CONCURRENT ); dispatch_sync(myqueue, ^{
[self heavyTask3];
});
dispatch_sync(myqueue, ^{
[self heavyTask1];
});
dispatch_sync(myqueue, ^{
[self heavyTask2];
});
NSLog(@"%s end",__PRETTY_FUNCTION__);
}
注意 queue 是 并发的,但是并没有并发执行。其次注意 serialGCD2 bengin 和 end 的位置,可与 serialGCD1 进行对比。
2014-06-30 01:11:37.100ConcurrentProgramingExample[2726:60b] -[XxpViewController serialGCD2] begin
2014-06-30 01:11:37.100ConcurrentProgramingExample[2726:60b] -[XxpViewController heavyTask3] begin
2014-06-30 01:11:40.102ConcurrentProgramingExample[2726:60b] -[XxpViewController heavyTask3] end
2014-06-30 01:11:40.103ConcurrentProgramingExample[2726:60b] -[XxpViewController heavyTask1] begin
2014-06-30 01:11:41.104ConcurrentProgramingExample[2726:60b] -[XxpViewController heavyTask1] end
2014-06-30 01:11:41.105ConcurrentProgramingExample[2726:60b] -[XxpViewController heavyTask2] begin
2014-06-30 01:11:43.107ConcurrentProgramingExample[2726:60b] -[XxpViewController heavyTask2] end
2014-06-30 01:11:43.107ConcurrentProgramingExample[2726:60b] -[XxpViewController serialGCD2] end
如何区分 dispatch_async dispatch_sync , queue(DISPATCH_QUEUE_CONCURRENT)和 queue(DISPATCH_QUEUE_SERIAL) ?
前两个 是 指 添加 任务block 到 queue 的方式。
后两个 是 queue 中执行 任务的 方式。
Tips :不管是执行 同步 /异步任务,都应该 使用 dispatch_async 进行添加 该任务到 queue 中。使用 dispatch_sync 会阻塞当前线程。
另外,绝对不要在任务 中 ,再次使用 dispatch_sync 同步新任务 到 当前queue 。对于串行queue,这样必定会造成死锁。对于 并发 queue ,也应该避免这样做。想想为什么:) ?
- dispatch group 可以 阻塞 一个 线程,直到 组内 任务 执行完成,才向下执行,并且组内任务完成后,会得到一个 任务完成时 通知。
-(void)dispatchGroupDemo{
NSLog(@"%s begin",__PRETTY_FUNCTION__);
dispatch_queue_t myqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t mygroup = dispatch_group_create();
dispatch_group_async(mygroup, myqueue, ^{
[self heavyTask2];
});
dispatch_group_notify(mygroup, myqueue, ^{
[self heavyTask1];
});
dispatch_group_wait(mygroup, DISPATCH_TIME_FOREVER); //block thethread
dispatch_group_async(mygroup, myqueue, ^{
[self heavyTask3];
});
NSLog(@"%s end",__PRETTY_FUNCTION__);
}
使用 dispatch_group_create() 创建 一个 mygroup .Dispatch_group_async 添加 任务 block 到 myqueue . 然后 dispatch_group_notify 添加 任务 完成 时的 通知block 。
使用 dispatch_goup_wait 阻塞 当前 线程(留意dispatchGroupDemo end 的时机,是在 heavy task2 end 之后才得以运行 ) 。后面又添加了一个 任务到 myqueue 中。
2014-06-30 21:56:23.171ConcurrentProgramingExample[5449:60b] -[XxpViewController dispatchGroupDemo]begin
2014-06-30 21:56:23.172ConcurrentProgramingExample[5449:1303] -[XxpViewController heavyTask2] begin
2014-06-30 21:56:25.174ConcurrentProgramingExample[5449:1303] -[XxpViewController heavyTask2] end
2014-06-30 21:56:25.175ConcurrentProgramingExample[5449:60b] -[XxpViewController dispatchGroupDemo]end
2014-06-30 21:56:25.175ConcurrentProgramingExample[5449:1303] -[XxpViewController heavyTask1] begin
2014-06-30 21:56:25.175ConcurrentProgramingExample[5449:3a03] -[XxpViewController heavyTask3] begin
2014-06-30 21:56:26.176ConcurrentProgramingExample[5449:1303] -[XxpViewController heavyTask1] end
2014-06-30 21:56:28.177ConcurrentProgramingExample[5449:3a03] -[XxpViewController heavyTask3] end
如果 把 dispatch_group_wait 屏蔽掉,是怎样的效果?
2014-06-30 21:58:37.244ConcurrentProgramingExample[5466:60b] -[XxpViewController dispatchGroupDemo]begin
2014-06-30 21:58:37.245ConcurrentProgramingExample[5466:60b] -[XxpViewController dispatchGroupDemo]end
2014-06-30 21:58:37.245ConcurrentProgramingExample[5466:1303] -[XxpViewController heavyTask2] begin
2014-06-30 21:58:37.245ConcurrentProgramingExample[5466:3407] -[XxpViewController heavyTask3] begin
2014-06-30 21:58:39.247ConcurrentProgramingExample[5466:1303] -[XxpViewController heavyTask2] end
2014-06-30 21:58:40.248ConcurrentProgramingExample[5466:3407] -[XxpViewController heavyTask3] end
2014-06-30 21:58:40.249ConcurrentProgramingExample[5466:3407] -[XxpViewController heavyTask1] begin
2014-06-30 21:58:41.250ConcurrentProgramingExample[5466:3407] -[XxpViewController heavyTask1] end
- Dispatch_barrier_async 隔断派发 。以 使用 此 派发方法 添加 任务block 到 queue 的时机为界线,在 此 之前的 任务 block 完成 后,才会开始执行此 任务 block . 在此 之后添加 的 任务 block ,需要等到 此 block 执行完成 后,才开始 执行。(注意 barrier 方式 添加任务 的 目标 queue ,必须是 自行创建的 并发queue )
-(void)dispatchbarrierDemo{
NSLog(@"%s begin",__PRETTY_FUNCTION__);
dispatch_queue_t myqueue = dispatch_queue_create("com.boyaa.texas.barrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myqueue, ^{
[self heavyTask3];
});
dispatch_barrier_async(myqueue, ^{
[self heavyTask1];
});
dispatch_async(myqueue, ^{
[self heavyTask2];
});
NSLog(@"%s end",__PRETTY_FUNCTION__);
}
异步派发 task3 ,然后 隔断派发 task1 ,最后 异步派发 task2 .执行 效果 为 ,task3 完成后,开始 task1 ,task1完成后,开始 task2.
2014-06-30 22:14:33.252ConcurrentProgramingExample[5579:60b] -[XxpViewController dispatchbarrierDemo]begin
2014-06-30 22:14:33.253ConcurrentProgramingExample[5579:60b] -[XxpViewController dispatchbarrierDemo]end
2014-06-30 22:14:33.253ConcurrentProgramingExample[5579:1303] -[XxpViewController heavyTask3] begin
2014-06-30 22:14:36.255ConcurrentProgramingExample[5579:1303] -[XxpViewController heavyTask3] end
2014-06-30 22:14:36.256ConcurrentProgramingExample[5579:1303] -[XxpViewController heavyTask1] begin
2014-06-30 22:14:37.257ConcurrentProgramingExample[5579:1303] -[XxpViewController heavyTask1] end
2014-06-30 22:14:37.258ConcurrentProgramingExample[5579:1303] -[XxpViewController heavyTask2] begin
2014-06-30 22:14:39.260ConcurrentProgramingExample[5579:1303] -[XxpViewController heavyTask2] end
Tips:
- 执行完成通知更新主线程
在 执行 任务的 block 里面 ,再次 dispatch 需要在主线程运行的任务 到 dispatch_get_main_queue()
-(void)doHeavyTaskAndThenUpdateUI{
NSLog(@"%s begin",__PRETTY_FUNCTION__);
dispatch_queue_t myqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(myqueue, ^{
[self heavyTask3];
NSString *str = @" get data here";
dispatch_async(dispatch_get_main_queue(),^{
//here update UI
UILabel * label = [[UILabel alloc] initWithFrame: CGRectMake(30, 100, 300, 30)];
label.text = str;
[self.view addSubview:label];
});
});
NSLog(@"%s end",__PRETTY_FUNCTION__);
}
在 queue 中,获取数据str 后,在 当前的 view 中创建一个 label ,并把内容显示出来
- 并发迭代。如果每次 迭代直接 没有 影响。可使用 dispatch_apply 并发地执行。
-(void)dispatchApply{
int count = 10;
dispatch_apply(count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t i) {
NSLog(@"current %zu",i);
});
}
注意执行顺序是无序的
2014-06-30 01:35:17.496ConcurrentProgramingExample[2845:60b] current 1
2014-06-30 01:35:17.496ConcurrentProgramingExample[2845:3a03] current 2
2014-06-30 01:35:17.496ConcurrentProgramingExample[2845:3b03] current 3
2014-06-30 01:35:17.497ConcurrentProgramingExample[2845:60b] current 4
2014-06-30 01:35:17.496ConcurrentProgramingExample[2845:1303] current 0
2014-06-30 01:35:17.498ConcurrentProgramingExample[2845:3a03] current 5
2014-06-30 01:35:17.500ConcurrentProgramingExample[2845:60b] current 7
2014-06-30 01:35:17.500ConcurrentProgramingExample[2845:3b03] current 6
2014-06-30 01:35:17.501ConcurrentProgramingExample[2845:1303] current 8
2014-06-30 01:35:17.501ConcurrentProgramingExample[2845:3a03] current 9
- 后缀 _f 和 没有 _f dispatch 系列函数是相同的 功能,不同 的是 使用 函数指针 而不是 block/对象 提交任务 。
void heavyTask4_f(){
NSLog(@"%s begin",__PRETTY_FUNCTION__);
[NSThread sleepForTimeInterval:4];
NSLog(@"%s end",__PRETTY_FUNCTION__);
}
-(void)dispatchbarrier_fDemo{
NSLog(@"%s begin",__PRETTY_FUNCTION__);
dispatch_queue_t myqueue = dispatch_queue_create("com.boyaa.texas.barrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myqueue, ^{
[self heavyTask3];
});
dispatch_barrier_async_f(myqueue, nil, &heavyTask4_f);
dispatch_async(myqueue, ^{
[self heavyTask2];
});
NSLog(@"%s end",__PRETTY_FUNCTION__);
}
- 消耗大内存的任务 应自行创建局部 autotreleasepool ,提交释放内存
- 挂起 和 继续 执行 queue ,以及 挂起计数。使用 挂起 suspend可以 暂停 挂起queue后 添加的任务block。此时 挂起 计数 +1 。使用 恢复 resume ,挂起 计数 -1 .当挂起计数 为 0 时,可以 继续运行 前面被挂起的 任务 block 。
dispatch_suspend 挂起 计数 +1
| 挂起queue ,运行完当前的 queue 的所有任务block 后,暂停运行后面添加的block . |
dispatch_resume 挂起计数 -1
| 当 挂起计数 为 0 时,运行完当前 queue 的所有任务 block 后,运行suspend 之后添加到 queue 的任务。 |
-(void)dispatch_suspend_resume{
NSLog(@"%s begin",__PRETTY_FUNCTION__);
myxxpQueue = dispatch_queue_create("com.boyaa.texas.resume", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myxxpQueue, ^{
[self heavyTask3];
});
dispatch_async(myxxpQueue, ^{
[self heavyTask1];
dispatch_resume(myxxpQueue);
});
dispatch_suspend(myxxpQueue);
dispatch_async(myxxpQueue, ^{
[self heavyTask2];
});
NSLog(@"%s end",__PRETTY_FUNCTION__);
}
2014-06-30 23:04:11.546ConcurrentProgramingExample[6059:60b] -[XxpViewControllerdispatch_suspend_resume] begin
2014-06-30 23:04:11.547ConcurrentProgramingExample[6059:60b] -[XxpViewControllerdispatch_suspend_resume] end
2014-06-30 23:04:11.547ConcurrentProgramingExample[6059:1303] -[XxpViewController heavyTask3] begin
2014-06-30 23:04:11.547ConcurrentProgramingExample[6059:3903] -[XxpViewController heavyTask1] begin
2014-06-30 23:04:12.549ConcurrentProgramingExample[6059:3903] -[XxpViewController heavyTask1] end
2014-06-30 23:04:14.549ConcurrentProgramingExample[6059:1303] -[XxpViewController heavyTask3] end
2014-06-30 23:04:14.550ConcurrentProgramingExample[6059:1303] -[XxpViewController heavyTask2] begin
2014-06-30 23:04:16.551ConcurrentProgramingExample[6059:1303] -[XxpViewController heavyTask2] end
-----------
参考资料:http://blog.csdn.net/totogo2010/article/details/8016129
《iOS 并发编程指南 》 ,kevin 翻译,http://www.gungyi.com