Dispatch Semaphore 是持有计数的信号量。
- dispatch_semaphore_create 创建一个信号量的初始值
传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量。
值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL。
- dispatch_semaphore_signal => 发送一个信号 这个函数会使传入的信号量dsema的值加1。
返回值为long类型,当返回值为0时表示当前并没有线程等待其处理的信号量,其处理的信号量的值加1即可。
当返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。- dispatch_semaphore_wait => 等待一个信号
这个函数会使传入的信号量dsema的值减1;
如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;
如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,
不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,
且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。
如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。
- timeout
在设置timeout时,比较有用的两个宏:
DISPATCH_TIME_NOW 表示当前;
DISPATCH_TIME_FOREVER 表示未来;
一般可以直接设置timeout为这两个宏其中的一个,或者自己创建一个dispatch_time_t类型的变量。
创建dispatch_time_t类型的变量有两种方法,dispatch_time和dispatch_walltime。
利用创建dispatch_time创建dispatch_time_t类型变量的时候一般也会用到这两个变量。
dispatch_time的声明如下:
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
其参数when需传入一个dispatch_time_t类型的变量,和一个delta值。表示when加delta时间就是timeout的时间。
例如:
dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, 110001000*1000);
表示当前时间向后延时一秒为timeout的时间。
备注:
关于时间的一些宏
#define NSEC_PER_SEC 1000000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
NSEC:纳秒。
USEC:微秒。
SEC:秒
PER:每
1 NSEC_PER_SEC,每秒有多少纳秒。
2 USEC_PER_SEC,每秒有多少毫秒。(注意是指在纳秒的基础上)
3 NSEC_PER_USEC,每毫秒有多少纳秒。
例1:使用dispatch semaphore去实现等待Block操作结束。
单个block
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[self methodWithABlock:^(id result){
//写block中做的事情
//结束等待
dispatch_semaphore_signal(sem);
}];
//等待信号
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
多个block
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[self methodWithABlock:^(id result){
//写block中做的事情
dispatch_semaphore_signal(sem);
}];
[self methodWithABlock:^(id result){
//写block中做的事情
dispatch_semaphore_signal(sem);
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
block中的block
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[self methodWithABlock:^(id result){
//写block中做的事情
dispatch_semaphore_signal(sem);
[self methodWithABlock:^(id result){
//写block中做的事情
dispatch_semaphore_signal(sem);
}];
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
iOS 等待block执行完成(dispacth semaphore)
实例2:dispatch_semaphore_t 造成 EXC_BAD_INSTRUCTION 崩溃
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i < 4; i++) {
NSString *str = [NSString stringWithFormat:@"%d-signal", i];
[array addObject:str];
printf("end -- %d\n",i);
}
void (^block)(NSString *text) = ^(NSString *text) {
NSLog(@"%@", text);
dispatch_semaphore_signal(semaphore);
};
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
[array enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if(idx > 1) {
*stop = YES;
} else {
block(obj);
}
NSLog(@"wait------");
}];
});
}
运行结果
2020-05-09 21:34:01.275179+0800 OCTestFirst[10536:298054] 0-signal
2020-05-09 21:34:01.275371+0800 OCTestFirst[10536:298054] wait------
2020-05-09 21:34:01.275499+0800 OCTestFirst[10536:298054] 1-signal
2020-05-09 21:34:01.275664+0800 OCTestFirst[10536:298054] wait------
2020-05-09 21:34:01.275789+0800 OCTestFirst[10536:298054] wait------
提示:Thread 4: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0 错误,断点断在了_destroy_helper_block 这里的问题所在是在_dispatch_semaphore_dispose上。
代码改为
NSMutableArray *array = [NSMutableArray array];
__block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
for (int i = 0; i < 4; i++) {
NSString *str = [NSString stringWithFormat:@"%d-signal", i];
[array addObject:str];
}
void (^block)(NSString *text) = ^(NSString *text) {
NSLog(@"%@", text);
dispatch_semaphore_signal(semaphore);
};
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
[array enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if(idx > 1) {
*stop = YES;
dispatch_semaphore_signal(semaphore);
NSLog(@"%@", obj);
} else {
block(obj);
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"wait------");
}];
});
运行结果
2019-07-26 11:41:13.759351+0800 CCNetwork[99872:22495958] 0-signal
2019-07-26 11:41:13.759495+0800 CCNetwork[99872:22495958] wait------
2019-07-26 11:41:13.759579+0800 CCNetwork[99872:22495958] 1-signal
2019-07-26 11:41:13.759657+0800 CCNetwork[99872:22495958] wait------
2019-07-26 11:41:13.759734+0800 CCNetwork[99872:22495958] 2-signal
2019-07-26 11:41:13.759810+0800 CCNetwork[99872:22495958] wait------
比对下前后两段代码和打印的日志可以看出:
- 代码1,初始信号量为1,代码执行了2次dispatch_semaphore_signal,3次的dispatch_semaphore_wait。所以在代码运行结束前信号量为0.
- 代码2,初始信号量为0,代码执行了3次的dispatch_semaphore_signal,3次的dispatch_semaphore_wait。所以在代码运行结束前信号量也是为0.
代码3
NSMutableArray *array = [NSMutableArray array];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
for (int i = 0; i < 4; i++) {
NSString *str = [NSString stringWithFormat:@"%d-signal", i];
[array addObject:str];
}
void (^block)(NSString *text) = ^(NSString *text) {
NSLog(@"%@", text);
dispatch_semaphore_signal(semaphore);
};
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
[array enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if(idx > 1) {
*stop = YES;
dispatch_semaphore_signal(semaphore);
NSLog(@"%@", obj);
} else {
block(obj);
}
NSLog(@"wait------");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}];
dispatch_semaphore_signal(semaphore);
NSLog(@"3-signal");
});
运行结果日志:
2020-05-09 21:55:43.344631+0800 OCTestFirst[10830:309518] 0-signal
2020-05-09 21:55:43.344760+0800 OCTestFirst[10830:309602] wait------
2020-05-09 21:55:43.344869+0800 OCTestFirst[10830:309602] 1-signal
2020-05-09 21:55:43.344760+0800 OCTestFirst[10830:309602] wait------
2020-05-09 21:55:43.345057+0800 OCTestFirst[10830:309602] 2-signal
2020-05-09 21:55:43.344760+0800 OCTestFirst[10830:309602] wait------
2020-05-09 21:55:43.345350+0800 OCTestFirst[10830:309602] 3-signal
代码3能够正常运行,不会引起崩溃。由运行结果可以看出,代码3,初始信号量为0,代码执行了4次的dispatch_semaphore_signal,1次的dispatch_semaphore_wait。所以在代码运行结束前信号量是1.
代码3能够正常运行,不会引起崩溃。由运行结果可以看出,代码3,初始信号量为0,代码执行了4次的dispatch_semaphore_signal,3次的dispatch_semaphore_wait。所以在代码运行结束前信号量也是为1.
所以我们可以得到一个结论:
dispatch_semaphore_t调用_dispatch_semaphore_dispose 释放时,当前信号量值必须大于等于初始信号量值时,才能正常释放,否则将引起EXC_BAD_INSTRUCTION指令错误。
另外在dispatch_semaphore_t 处于wait状态时,释放这个dispatch_semaphore_t的引用(sema = nil),也会导致崩溃
dispatch_semaphore崩溃
遇到的 dispatch_group_t 类似错误崩溃时也可以借鉴dispatch_semaphore_t崩溃原因进行联想
group进行额外的leave操作从而造成crash(注:b1到b4因为leave的需要,会对group进行地址引用)
dispatch_semaphore_t 造成 EXC_BAD_INSTRUCTION 崩溃
实例3:dispatch_barrier_async
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"任务A");
});
dispatch_async(queue, ^{
NSLog(@"任务B");
});
dispatch_async(queue, ^{
NSLog(@"任务C");
});
dispatch_barrier_async(queue, ^{
NSLog(@"阻塞自定义并发队列");
});
dispatch_async(queue, ^{
NSLog(@"任务D");
});
dispatch_async(queue, ^{
NSLog(@"任务E");
});
打印结果
2020-05-10 07:40:48.000366+0800 OCTestFirst[1800:33976] 阻塞自定义并发队列
2020-05-10 07:40:48.000398+0800 OCTestFirst[1800:33980] 任务A
2020-05-10 07:40:48.000397+0800 OCTestFirst[1800:33981] 任务B
2020-05-10 07:40:48.000413+0800 OCTestFirst[1800:33984] 任务C
2020-05-10 07:40:48.000565+0800 OCTestFirst[1800:33976] 任务D
2020-05-10 07:40:48.000606+0800 OCTestFirst[1800:33980] 任务E
注意:注意,这里用到的dispatch_barrier_async如果使用的队列是dispatch_global_queue,那么就等同意dispatch_async,起不到阻塞的作用。我们需要自己创建并发队列,然后再执行barrier函数,前面ABC三个任务随机,后面DE随机,但是DE的执行必须是等待ABC任务执行完的
如果将上例中queue改为自己创建的并发队列
dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);
打印结果
2020-05-10 07:43:39.783157+0800 OCTestFirst[1857:36354] 任务C
2020-05-10 07:43:39.783185+0800 OCTestFirst[1857:36352] 任务B
2020-05-10 07:43:39.783202+0800 OCTestFirst[1857:36351] 任务A
2020-05-10 07:43:39.783452+0800 OCTestFirst[1857:36351] 阻塞自定义并发队列
2020-05-10 07:43:39.783587+0800 OCTestFirst[1857:36351] 任务D
2020-05-10 07:43:39.783591+0800 OCTestFirst[1857:36352] 任务E
可以看到,上面的都是异步并发操作,而里面的任务是同步的,这里的任务指的是Block里面的所有操作,但是如果Block里面的操作是网络请求,也是异步的,那上面的做法就会有问题了,看如下代码
🌰
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"同步任务A");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"网络异步任务AA");
});
});
dispatch_group_async(group, queue, ^{
NSLog(@"同步任务B");
});
dispatch_group_async(group, queue, ^{
NSLog(@"同步任务C");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"任务完成执行");
});
// 2020-05-10 08:24:07.117888+0800 OCTestFirst[2279:50892] 同步任务A
// 2020-05-10 08:24:07.118070+0800 OCTestFirst[2279:50895] 同步任务B
// 2020-05-10 08:24:07.118106+0800 OCTestFirst[2279:50988] 同步任务C
// 2020-05-10 08:24:07.119289+0800 OCTestFirst[2279:50895] 任务完成执行
// 2020-05-10 08:24:09.118818+0800 OCTestFirst[2279:50803] 网络异步任务AA
用dispatch_after来模拟网络请求,可以看到,同步任务ABC->D这些操作还是正确的,但是里面有个模拟网络请求的任务,就不会按我们所想的顺序执行了。为什么呢?很简单,首先都是异步的,子线程操作,而且是并发队列,那么任务可以多个一起,如果任务是单纯的打印,即同步任务,那么就能完成我们的预期,如果Block任务里面还嵌套异步任务,因为并发队列里面的任务,只是负责打印和发送请求的操作,异步回调数据是不归队列管的,任务的执行完毕,只是Block代码块代码执行完,如果里面还包含异步任务,这里就需要通过信号量dispatch_semaphore来实现了。
上一题中的ABC三个任务改成异步任务(如AFN网络请求)、全部回调成功后进行数据整合。
如果只说队列/任务组肯定不行。因为网络请求本身是异步的、任务会立即完成、但数据还没有回来。
最好的就是在队列组的前提下。把异步的网络请求转化为同步、以捕获正确的完成时机。
具体操作需要使用信号量。
第一种 dispatch_group + semaphore
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_group_async(group, queue, ^{
NSLog(@"同步任务A");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"网络异步任务一");
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
dispatch_group_async(group, queue, ^{
NSLog(@"同步任务B");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"网络异步任务二");
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
dispatch_group_async(group, queue, ^{
NSLog(@"同步任务C");
});
dispatch_group_async(group, queue, ^{
NSLog(@"同步任务D");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"网络异步任务四");
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"任务完成执行");
});
打印结果
可以看到,Block整个就是一个任务,如果没有dispatch_semaphore_wait,带有网络请求的任务,因为网络请求本身是异步的、任务会立即完成、但数据还没有回来。,因为发送网络请求,就已经把Block的任务完成了,异步回来的操作已经不属于并发队列里面的管理的任务了。就和上面的错误例子一样,完成任务的执行,和请求回调没有任何顺序关系了。那么如果我们在Block里面加了dispatch_semaphore_wait,什么意思呢?如果信号量为0的时候,那么就会一直在这里等待。可以理解为一开始发送网络请求出去,这个时候执行到wait函数,信号量为0,等待,队列任务没有执行完,只有当请求回来的时候调用singnal的时候,信号+1,wait的函数随机获取到信号,放开任务,执行完毕一个,剩下的没有获取到的信号继续等待,那么就会按我们的,执行完一个网络请求,信号+1,释放一个wait,执行完一个Block任务,那么,当所有的网络请求执行完,所有的wait都被释放,任务都完成了,才会通知Group调用完成。
第二种 dispatch_group_enter 和 dispatch_group_leave
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"同步任务A");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"网络异步任务一");
dispatch_group_leave(group);
});
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"同步任务B");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"网络异步任务二");
dispatch_group_leave(group);
});
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"同步任务C");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"同步任务D");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"网络异步任务四");
dispatch_group_leave(group);
});
});
dispatch_group_notify(group, queue, ^{
NSLog(@"任务完成执行");
});
这种方法也是我之前项目中用的,也能实现上面的执行顺序。
ABCD异步顺序随机,无论任务是否有异步,都会等任务执行完(包括网络请求)再执行任务完成
总结
总结
异步并发执行同步任务,可以用dispatch_group,dispatch_barrier和NSOperation的依赖
异步串行执行同步任务,默认就是顺序执行
异步并发执行异步任务,可以用dispatch_group+semaphore 初始化信号为0,执行完一个异步通过singnal释放一个wait任务
或者用disaptch_group_enter 和 dispatch_group_leave
如果多个任务网络请求,NSOperation我感觉无法实现 多网络任务执行完之后再最终统一执行某一个人任务。这个时候只有用dispatch_group + semaphore来实现
异步并发下的任务是网络请求信号量的使用