关于信号量 dispatch_semaphore 的一些个人思考
信号量,表示了一个数字。
每一个执行的线程,可以向这个信号量申请一个数字。
如果申请到了,总信号量-1。当前线程执行完毕,信号量+1。
相关场景1
当某个线程需要执行某个任务的时候,向信号量请求1个数字。
如果信号量剩余的数字大于0,那么请求就成功。
且当前线程,会将此信号量的数字-1。
当这个线程执行完毕之后,要将这个信号量的数字+1。
这是很多博客里,关于信号量的使用的“标准答案。
当然,那种放荡不羁的线程,压根就不想和信号量产生半毛钱关系。
自己想执行就执行,完全不在乎信号量是否存在。
但随着而来的,就可以产生数据的混乱。
信号量只是一种线程主动愿意被信号控制的手段。
下面几点,是对信号量的几种非常规的测试。并不代表这一定是正确的。
我非不要 dispatch_semaphore_wait & dispatch_semaphore_signal 一起使用。
获取到的信号量-1操作,完成之后,就不把信号量+1.
#pragma mark - 信号量,-1之后,不+1.
- (void)semaphoreDemo1 {
// 1.创建一个信号为1的信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
// 2.当前 UI 线程获取这个信号量
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 3.拿到了信号,执行方法
NSLog(@"%@",@"我是 UI 主线程,我拿到了信号量。");
// dispatch_semaphore_signal(semaphore); // 不把信号量+1
}
运行结果:
程序直接崩溃了。
但这个是在主线程。我换子线程试试?
在子线程,信号量-1,不+1
- (void)semaphoreDemo2 {
// 1.创建一个信号为1的信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%@",@"我是子线程,我拿到了1个信号");
// dispatch_semaphore_signal(semaphore); // 不把信号量+1
});
NSLog(@"%@",@"主线程执行");
}
运行结果:
[图片上传失败...(image-a4bd20-1510308500036)]
发现仍然会崩溃。
崩溃信息是:
bug in client of libdispatch semaphore object deallocated while in use.
好像表明了,我在使用一个释放了的信号量对象。
OK,那我把信号量设置成全局的再试一次。
@implementation ViewController {
dispatch_semaphore_t _semaphore;
}
- (void)semaphoreDemo2 {
// 1.创建一个信号为1的信号量
_semaphore = dispatch_semaphore_create(1);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%@",@"我是子线程,我拿到了1个信号");
// dispatch_semaphore_signal(_semaphore); // 不把信号量+1
});
NSLog(@"%@",@"主线程执行");
}
点击屏幕第一下。
运行结果:
[ViewController.m] :[63] [主线程执行]
[ViewController.m] :[59] [我是子线程,我拿到了1个信号]
程序运行正常。
然后,毫无意识了点击了屏幕第二下。
程序崩溃。
现在有:
- 一个全局的信号量属性。
- 初始化的时候,设置了信号量为1.
- 点击屏幕的时候,拿到一个信号量,此时的信号量为0.这个全局属性信号量的为0
- 第二次点击屏幕的时候,重复第一步。
但在第一步的时候报错了。
为什么一个新的信号量属性无法创建呢?
现在唯一的猜测:
难道是因为前一个属性的信号量 -1了,没有执行+1的操作?
然后上代码,使用完毕之后,把信号量+1.
- (void)semaphoreDemo2 {
// 1.创建一个信号为1的信号量
_semaphore = dispatch_semaphore_create(1);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%@",@"我是子线程,我拿到了1个信号");
// 使用完毕之后,把信号量+1.
dispatch_semaphore_signal(_semaphore);
});
NSLog(@"%@",@"主线程执行");
}
继续点击屏幕两次。
[ViewController.m] :[64] [主线程执行]
[ViewController.m] :[59] [我是子线程,我拿到了1个信号]
[ViewController.m] :[64] [主线程执行]
[ViewController.m] :[59] [我是子线程,我拿到了1个信号]
运行变的正常了。
说明了:
信号量在使用的时候,-1 和 + 1操作,尽量要成对出现。否则程序可能会崩溃。
至于为什么要这样,我也不知道。
那么在主线程,信号量崩溃的原因,也是因为信号量没有按照使用 -1 + 1 吗?
- (void)semaphoreDemo1 {
// 1.创建一个信号为1的信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
// 2.当前 UI 线程获取这个信号量
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 3.拿到了信号,执行方法
NSLog(@"%@",@"我是 UI 主线程,我拿到了信号量。");
// 按照信号量的使用规则 -1 +1.不管在哪个线程,都应该这样。
dispatch_semaphore_signal(semaphore);
}
运行结果:
[ViewController.m] :[47] [我是 UI 主线程,我拿到了信号量。]
[ViewController.m] :[47] [我是 UI 主线程,我拿到了信号量。]
发现一切正常。
所以,第一条结论:
使用信号量的时候,dispatch_semaphore_wait
& dispatch_semaphore_signal
一定要成对出现。
都说如果 dispatch_semaphore_wait 如果请求的信号 == 0的话,当前线程会被阻塞。
试试看。
主线程,贱贱的拿走仅有的一个信号。
导致异步子线程在拿信号的时候就没有了。
如果说 dispatch_semaphore_wait
是等待的话,那么子线程 NSLog() 是无法输出的。
- (void)demo3 {
_semaphore = dispatch_semaphore_create(1);
// 主线程拿了信号了。
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 子线程,拿不到这个信号,除非主线程 signal 一下。
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%@",@"完了,我被卡死了。");
});
NSLog(@"%@",@"我就不把信号还原");
}
运行结果:
[ViewController.m] :[81] [我就不把信号还原]
测试结果:
的确如此。
dispatch_semaphore_wait 如果拿不到信号,会阻塞当前线程。
改进: 当主线程嘚瑟2秒,才还原信号。
- (void)demo3 {
_semaphore = dispatch_semaphore_create(1);
// 主线程拿了信号了。
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 子线程,拿不到这个信号,除非主线程 signal 一下。
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%@",@"完了,我被卡死了。");
// 子线程也乖乖的把信号还原
dispatch_semaphore_signal(_semaphore);
});
NSLog(@"%@",@"我就不把信号还原,让我嘚瑟2秒");
sleep(2);
NSLog(@"还是把信号还原吧。")
dispatch_semaphore_signal(_semaphore);
}
运行结果:
[ViewController.m] :[81] [我就不把信号还原,让我嘚瑟2秒]
[ViewController.m] :[85] [还是把信号还原吧。]
[ViewController.m] :[78] [完了,我被卡死了。]
第二个结论:
dispatch_semaphore_wait会尝试从信号源获取一个信号,如果获取到了,当前线程继续执行。
如果获取不到,就一直等待,直到别的线程释放了一个信号为止。
使用 dispatch_semaphore_create 创建信号源数量是固定的吗?
使用 dispatch_semaphore_create
。需要传入一个 long 的数值,表示信号源个数。
使用的时候,信号源 -1 ,使用完毕信号源 + 1.
但从来没有看到说,信号源的数量是一开始就指定和是只读的吗?
我能修改这个数量吗?
试试看。
- 创建一个信号为1 的信号源
- 连续3次 signal ,信号源变成4.(如果可以的话)
- 主线程拿走一个信号源,还剩下3个。
- 开启3个子线程,每个人都分别走剩下的3个信号源。
- 如果子线程代码输入的线程编号是3个不一样的数字,那么结论就是成立的。(什么结论?)
- (void)demo4 {
// 默认信号源是1.
_semaphore = dispatch_semaphore_create(1);
// 信号源+1
dispatch_semaphore_signal(_semaphore);
// 信号源+1
dispatch_semaphore_signal(_semaphore);
// 信号源+1
dispatch_semaphore_signal(_semaphore);
// 现在有4个信号源了。至少大于1个。
// 证明一下。
// 主线程那走1个,还剩下3个。
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%@",@"主线程执行完毕。");
// 先不还原
for (NSInteger i = 0; i < 3; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 子线程拿信号源
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
// 如果这里能打印出3个不同的线程,就说明一开始传入的那个 long 参数,并不是不可以修改的。
NSLog(@"%@",[NSThread currentThread]);
});
}
}
运行结果:
[ViewController.m] :[114] [主线程执行完毕。]
[ViewController.m] :[121] [<NSThread: 0x610000077f40>{number = 4, name = (null)}]
[ViewController.m] :[121] [<NSThread: 0x600000078b00>{number = 3, name = (null)}]
[ViewController.m] :[121] [<NSThread: 0x618000077280>{number = 5, name = (null)}]
第三条结论
使用 dispatch_semaphore_t dispatch_semaphore_create(long value) 函数,传递进去的 long 并不是不可以修改的。
而是非常灵活的可以使用 disaptch_semaphore_signal 来随意的修改信号量的总量。
最后总结:
- 信号量是一个公用资源。
- 相关的不相关的线程都可以从同一个信号源申请信号。
- 如果信号 > 0 ,那么就应该手动的把信号 - 1
dispatch_semaphore_wait
。 - 使用完毕,则应该必须手动把信号 + 1
dispatch_semaphore_signal
。 - 如果不做这些应该做的事情,就会被无情的崩溃。
- 信号量一开始传递出进去的 long 参数,并不代码了信号量就只有那么多。
- 我们可以随意的使用
dispatch_semaphore_signal
增加信号。 - 为什么不测试
dispatch_semaphore_wait
- 信号?因为,这个方法如果拿不到信号,后面的代码就不执行了。第二点已经证明了这个观点。