这里的运行顺序都是指执行顺序,如果是异步回调方式的执行顺序要分情况。
1.group之并发异步
dispatch_queue_t queue = dispatch_queue_create("queue_test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"任务1——准备休眠3秒");
sleep(3);
NSLog(@"任务1——完成");
});
NSLog(@"主线程——准备休眠5秒");
sleep(5);
NSLog(@"主线休眠结束");
dispatch_group_async(group, queue, ^{// 等主线程休眠结束后才会开启新的线程
NSLog(@"任务2——准备休眠10秒");
sleep(10);
NSLog(@"任务2——完成");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"任务组完成");
});
NSLog(@"主线程结束");
运行结果
2018-04-18 14:50:56.552841+0800 GCDDEMO[95861:36902605] 主线程——准备休眠5秒
2018-04-18 14:50:56.552841+0800 GCDDEMO[95861:36902640] 任务1——准备休眠3秒
2018-04-18 14:50:59.557429+0800 GCDDEMO[95861:36902640] 任务1——完成
2018-04-18 14:51:01.554070+0800 GCDDEMO[95861:36902605] 主线休眠结束
2018-04-18 14:51:01.554208+0800 GCDDEMO[95861:36902605] 主线程结束
2018-04-18 14:51:01.554225+0800 GCDDEMO[95861:36902640] 任务2——准备休眠10秒
2018-04-18 14:51:11.558221+0800 GCDDEMO[95861:36902640] 任务2——完成
2018-04-18 14:51:11.558424+0800 GCDDEMO[95861:36902640] 任务组完成
-
dispatch_group_notify
任务组的结束通知可以添加多次、并且会多次调用。 - 类似于NSOperation中的依赖、GCD的group监听是可以追加的。只要任务组中的任务没有全部完成、group完成的监听就不会被调用、哪怕是后追加的任务。
dispatch_queue_t queue = dispatch_queue_create("queue_test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"任务1——准备休眠3秒");
sleep(3);
NSLog(@"任务1——完成");
});
NSLog(@"添加完成监听");
dispatch_group_notify(group, queue, ^{
NSLog(@"任务组完成");
});
dispatch_group_async(group, queue, ^{
NSLog(@"任务2——准备休眠5秒");
sleep(10);
NSLog(@"任务2——完成");
});
NSLog(@"主线程结束");
运行结果
2018-04-18 15:14:51.626574+0800 GCDDEMO[96182:36985276] 添加完成监听
2018-04-18 15:14:51.626574+0800 GCDDEMO[96182:36985448] 任务1——准备休眠3秒
2018-04-18 15:14:51.626747+0800 GCDDEMO[96182:36985276] 主线程结束
2018-04-18 15:14:51.626759+0800 GCDDEMO[96182:36985447] 任务2——准备休眠10秒
2018-04-18 15:14:54.628660+0800 GCDDEMO[96182:36985448] 任务1——完成
2018-04-18 15:15:01.630341+0800 GCDDEMO[96182:36985447] 任务2——完成
2018-04-18 15:15:01.630496+0800 GCDDEMO[96182:36985447] 任务组完成
- 任务组是可以跨队列监听的,不知道是不是这么用的?
dispatch_queue_t queue = dispatch_queue_create("queue_test", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("queue_test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue2, ^{
NSLog(@"任务1——准备休眠3秒");
sleep(3);
NSLog(@"任务1——完成");
});
NSLog(@"添加完成监听");
dispatch_group_notify(group, queue, ^{
NSLog(@"任务组完成");
});
NSLog(@"主线程结束");
运行结果
2018-04-18 15:36:39.651263+0800 GCDDEMO[96416:37053885] 添加完成监听
2018-04-18 15:36:39.651266+0800 GCDDEMO[96416:37054026] 任务1——准备休眠3秒
2018-04-18 15:36:39.651413+0800 GCDDEMO[96416:37053885] 主线程结束
2018-04-18 15:36:42.654309+0800 GCDDEMO[96416:37054026] 任务1——完成
2018-04-18 15:36:42.654467+0800 GCDDEMO[96416:37054026] 任务组完成
2.快速遍历dispatch_apply
GCD提供了多线程快速遍历的方法。需要注意的是:
- 由于多线程遍历、输出的下标未必按照顺序排列
- 本质上是一个同步任务,而内部会使用一个并行队列用异步任务进行遍历(所以如果需要的话,自己在外部开辟一个新的异步任务)。
NSArray *array = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主线程执行用户界面更新等操作");
});
});
运行结果
2018-04-18 16:33:52.624275+0800 GCDDEMO[410:37234099] 4: e
2018-04-18 16:33:52.624275+0800 GCDDEMO[410:37234101] 5: f
2018-04-18 16:33:52.624275+0800 GCDDEMO[410:37234102] 0: a
2018-04-18 16:33:52.624275+0800 GCDDEMO[410:37234108] 2: c
2018-04-18 16:33:52.624279+0800 GCDDEMO[410:37234109] 1: b
2018-04-18 16:33:52.624275+0800 GCDDEMO[410:37234100] 3: d
2018-04-18 16:33:52.624320+0800 GCDDEMO[410:37234124] 6: g
2018-04-18 16:33:52.624322+0800 GCDDEMO[410:37234125] 7: h
2018-04-18 16:33:52.624403+0800 GCDDEMO[410:37234101] 8: i
2018-04-18 16:33:52.624403+0800 GCDDEMO[410:37234099] 9: j
2018-04-18 16:33:52.640095+0800 GCDDEMO[410:37234054] 回到主线程执行用户界面更新等操作
3.GCD定时器
// 暂停定时器
- (IBAction)pause:(id)sender {
if (_timer) {
dispatch_suspend(_timer);
}
}
// 恢复定时器
- (IBAction)resume:(id)sender {// pause 一定要和resume成对使用,即使暂停后销毁stop也不行
if (_timer) {
dispatch_resume(_timer);
}
}
// 销毁定时器
- (IBAction)stop:(id)sender {
if (_timer) {
dispatch_source_cancel(_timer);
_timer = nil;
}
}
// 创建定时器
- (IBAction)start:(id)sender {
// 创建定时器,(dispatch_source_t本质是OC对象)
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
// 设置定时器的时间参数,时间参数一般是纳秒,(1秒 == 10的9次方纳秒)为单位
// 何时开始
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (ino64_t)(1.0 * NSEC_PER_SEC));
// 时间间隔
uint64_t interval = (uint64_t)(2.0 * NSEC_PER_SEC);
// 设置参数
dispatch_source_set_timer(self.timer, start, interval, 0);
// 设置回调,即设置需要定时器定时执行的操作
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"------");
});
dispatch_resume(self.timer);
}
也可以用于任务挂起和恢复
dispatch_queue_t myQueue;
myQueue = dispatch_queue_create("queue", NULL);
//挂起队列
NSLog(@"1");
dispatch_sync(myQueue, ^(){
NSLog(@"挂起任务之前");
});
NSLog(@"2");
dispatch_suspend(myQueue);
NSLog(@"3");
dispatch_sync(myQueue, ^(){
NSLog(@"挂起任务之后");
});
NSLog(@"4");
//恢复队列
dispatch_resume(myQueue);
执行结果
2018-04-19 09:47:11.089857+0800 GCDDEMO[17482:38678962] 1
2018-04-19 09:47:11.090024+0800 GCDDEMO[17482:38678962] 挂起任务之前
2018-04-19 09:47:11.090105+0800 GCDDEMO[17482:38678962] 2
2018-04-19 09:47:11.090162+0800 GCDDEMO[17482:38678962] 3
可以看到输出3之后后面就没有再输出任何log,所以需要外界一个条件触发dispatch_resume
来恢复队列
4.信号量semaphore
信号量类似于锁🔒
简单来讲,信号量为0则阻塞线程,大于0则不会阻塞。那么我们可以通过改变信号量的值,来控制是否阻塞线程,从而达到线程同步
GCD中的信号量含有三个函数
-
dispatch_semaphore_create
创建一个semaphore信号量 -
dispatch_semaphore_signal
发送一个信号让信号量+1 -
dispatch_semaphore_wait
如果信号量计数为0则阻塞等待、否则通过。
下面举两个例子
- 用semaphore控制并发数,通过设置信号量初始值,达到GCD的并发。
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建信号量,并且设置值为10
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 30; i++)
{ // 由于是异步执行的,所以每次循环Block里面的dispatch_semaphore_signal根本还没有执行就会执行dispatch_semaphore_wait,从而semaphore-1.当循环10此后,semaphore等于0,则会阻塞线程,直到执行了Block的dispatch_semaphore_signal 才会继续执行
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
NSLog(@"%i",i);
sleep(i);
// 每次发送信号则semaphore会+1,
dispatch_semaphore_signal(semaphore);
});
}
- 异步任务转同步
通过在方法末尾用信号量阻塞、知道异步请求完成后通过。
配合GCD任务组、NSOperation的依赖,可以达到多网络请求后的同步操作
NSString *urlStr = @"https://www.baidu.com";
NSURL *url = [NSURL URLWithString:urlStr];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i = 0; i < 10; i++) {
NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%d--%d",i,i);
dispatch_semaphore_signal(sem);
}];
[task resume];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"end~~~");
});
5.线程栅栏dispatch_barrier
线程栅栏可以阻塞某个queue
中任务的执行直到queue
中的栅栏之前任务被执行完毕。
dispatch_queue_t queue = dispatch_queue_create("test_queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 1; i <= 3; i ++) {
dispatch_async(queue, ^{
sleep(3);
NSLog(@"任务%d结束",i);
});
}
NSLog(@"代码经过栅栏");
dispatch_barrier_sync(queue, ^{
sleep(2);
NSLog(@"栅栏结束");
});
NSLog(@"代码通过栅栏");
for (int i = 4; i <= 6; i ++) {
dispatch_async(queue, ^{
sleep(3);
NSLog(@"任务%d结束",i);
});
}
NSLog(@"代码结束");
运行结果
2018-04-19 09:20:03.953580+0800 GCDDEMO[17144:38579855] 代码经过栅栏
2018-04-19 09:20:06.956880+0800 GCDDEMO[17144:38579951] 任务3结束
2018-04-19 09:20:06.956889+0800 GCDDEMO[17144:38579948] 任务1结束
2018-04-19 09:20:06.956880+0800 GCDDEMO[17144:38579950] 任务2结束
2018-04-19 09:20:08.958230+0800 GCDDEMO[17144:38579855] 栅栏结束
2018-04-19 09:20:08.958366+0800 GCDDEMO[17144:38579855] 代码通过栅栏
2018-04-19 09:20:08.958517+0800 GCDDEMO[17144:38579855] 代码结束
2018-04-19 09:20:11.962660+0800 GCDDEMO[17144:38579950] 任务6结束
2018-04-19 09:20:11.962660+0800 GCDDEMO[17144:38579951] 任务5结束
2018-04-19 09:20:11.962660+0800 GCDDEMO[17144:38579949] 任务4结束
如果把dispatch_barrier_sync
改成dispatch_barrier_async
之后运行结果
2018-04-19 09:21:31.528745+0800 GCDDEMO[17183:38585946] 代码经过栅栏
2018-04-19 09:21:31.528887+0800 GCDDEMO[17183:38585946] 代码通过栅栏
2018-04-19 09:21:31.528990+0800 GCDDEMO[17183:38585946] 代码结束
2018-04-19 09:21:34.531305+0800 GCDDEMO[17183:38586062] 任务1结束
2018-04-19 09:21:34.531305+0800 GCDDEMO[17183:38586064] 任务2结束
2018-04-19 09:21:34.531306+0800 GCDDEMO[17183:38586061] 任务3结束
2018-04-19 09:21:36.535029+0800 GCDDEMO[17183:38586064] 栅栏结束
2018-04-19 09:21:39.538077+0800 GCDDEMO[17183:38586064] 任务4结束
2018-04-19 09:21:39.538077+0800 GCDDEMO[17183:38586062] 任务6结束
2018-04-19 09:21:39.538077+0800 GCDDEMO[17183:38586061] 任务5结束
很明显的区别在于:同步栅栏会阻塞之后普通代码的执行、异步栅栏则不会。应用栅栏的特性、我们可以更好的做一些线程同步。某些情况下不需要写好几层任务组来同步任务。
例如4/5/6任务想要等待1/2/3任务。
用任务组的话、需要一个任务组包含1/2/3
然后在任务组完成的回调中再并发出三个任务4/5/6
而且还无法控制线程的阻塞、除非在想要阻塞的地方加入最后一个同步任务
想想就很麻烦……
原文移步这里