我们先来看个简单的例子:
代码1
NSMutableArray *array = [NSMutableArray array];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
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) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if(idx > 1) {
*stop = YES;
} else {
block(obj);
}
NSLog(@"wait------");
}];
});
运行结果日志:
2019-07-26 10:09:19.895115+0800 CCNetwork[97572:22432685] 0-signal
2019-07-26 10:09:19.895227+0800 CCNetwork[97572:22432685] wait------
2019-07-26 10:09:19.895316+0800 CCNetwork[97572:22432685] 1-signal
2019-07-26 10:09:19.895421+0800 CCNetwork[97572:22432685] wait------
2019-07-26 10:09:19.895515+0800 CCNetwork[97572:22432685] wait------
咋一看感觉没什么问题,但是如果去运行下,你会发现崩溃了。!
提示:Thread 4: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0 错误,断点断在了_destroy_helper_block。如果换个复杂的场景大家可能会把如果把重心放在_destroy_helper_block 上去寻找答案,会发现这条路非常崎岖,很难找到想要的答案。其实这里的问题所在是在_dispatch_semaphore_dispose上。
接下来我们把代码修改下
代码2
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.
推论:释放dispatch_semaphore_t调用_dispatch_semaphore_dispose 时,当前信号量值必须等于初始信号量值时能够正常运行,那么当前信号量的值大于初始信号量值呢?
我们把代码2修改下:
代码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");
});
运行结果日志:
2019-07-26 11:53:10.869757+0800 CCNetwork[168:22503815] 0-signal
2019-07-26 11:53:10.869917+0800 CCNetwork[168:22503815] wait------
2019-07-26 11:53:10.870023+0800 CCNetwork[168:22503815] 1-signal
2019-07-26 11:53:10.870115+0800 CCNetwork[168:22503815] wait------
2019-07-26 11:53:10.870211+0800 CCNetwork[168:22503815] 2-signal
2019-07-26 11:53:10.870295+0800 CCNetwork[168:22503815] wait------
2019-07-26 11:53:10.870381+0800 CCNetwork[168:22503815] 3-signal
代码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),也会导致崩溃具体可以参考跳转链接
(这个结论有误,感谢2楼细心指出) - 遇到的 dispatch_group_t 类似错误崩溃时也可以借鉴dispatch_semaphore_t崩溃原因进行联想,这里就不进行阐述了