dispatch_suspend/dispatch_resume
有个时候会有这样的需求场景,当追加大量的处理到Dispatch Queue中,在追加的处理的过程中,希望已经追加的处理不执行。例如,将要追加的block已经截获到了结果值,而已经追加的处理如果继续执行,则会改变这个值。
在这种情况下, 只需要挂起Dispatch Queue就可以,当可以执行时再恢复。
dispatch_suspend函数挂起指定的Dispatch Queue
dispatch_suspend(globalQueue);
dispatch_resume函数恢复指定的Dispatch Queue
dispatch_resume(globalQueue);
这些函数对已经执行的处理没有影响,挂起后,追加到Dispatch Queue中的但尚未执行的处理在此之后停止执行,恢复后继续执行。
dispatch_once
dispatch_once函数可以保证在应用程序的执行中,只执行一次指定的处理。即使在多线程环境下,也可以保证百分百安全。多用于单利模式
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"执行一次性的任务")
});
在xcode中是个默认的代码块,输入“dispatch_once”选择“dispatch_once snippet...”即可。
dispatch_semaphore
先看一段代码
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *marr = [[NSMutableArray alloc] init];
for (int i = 0; i < 10000; i++) {
dispatch_async(globalQueue, ^{
[marr addObject:@(i)];
NSLog(@"%d",i);
});
}
由于该源码使用Global Dispatch Queue更新NSMutableArray类的对象,很容易因为内存错误导致应用程序异常结束。这就需要用到dispatch_semaphore。
semaphore 是信号、旗语的意思,我们就在这里理解为信号通道吧。它的作用是把可同时执行多个线程的并行队列,压缩成只可以同时执行小于等于指定数量线程的队列。
dispatch_semaphore有三个函数
- dispatch_semaphore_create 创建信号通道
dispatch_semaphore_create(<#long value#>)
参数为允许信号个数,必须大于0,否则返回NULL,大于1则返回dispatch_semaphore_t类型对象,即一个允许的信号数为n的信号通道
- dispatch_semaphore_wait 等待当前的队列中线程数量和信号通道都满足条件
dispatch_semaphore_wait(<#dispatch_semaphore_t _Nonnull dsema#>, <#dispatch_time_t timeout#>)
第一个参数为dispatch_semaphore_t类型变量,即信号通道;
第二个参数为dispatch_time_t类型值,
当取值为DISPATCH_TIME_FOREVER时,程序就会一直等待,直到队列中的线程数大于等于1,且信号通道允许的信号数大于1,此时函数返回0,且程序继续往下执行。
如果此参数指定一个时间段,比喻说5s,如果在5s内队列中的线程数大于等于1,且信号通道允许的信号数大于1,则此函数返回0,并且程序继续往下执行,否则经过5s线程数依然小于等于0,或者信号通道允许的信号数小于1,则函数返回非0,程序继续往下执行。
另外,当dispatch_semaphore_wait函数返回0的同时,会将信号通道允许信号数减一。
- dispatch_semaphore_signal 使指定信号通道的允许信号数加一
dispatch_semaphore_signal(<#dispatch_semaphore_t _Nonnull dsema#>)
参数为指定的信号通道。
函数的功能是使信号通道允许信号数+1,然后唤醒等待的线程,唤醒成功返回非0,否则为0。
完整使用
// 创建一个信号通道,允许的信号数是1
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *marr = [[NSMutableArray alloc] init];
for (int i = 0; i < 10000; i++) {
dispatch_async(globalQueue, ^{
/* 等待队列中可执行的线程数大于等于1,如果在10s之内队列中可执行线程数大于等于1,继续判断信号通道允许的信号数是否大于1,大于1则往下执行,
同时函数dispatch_semaphore_wait返回0,且将信号通道允许的信号数-1(表示此时信号通道被占用),
这样信号通道的允许信号数为0(如果一直为0,队列中的任务在下一循环中将一直等待指定时间消耗完,程序才会往下执行,函数返回非0)
如果在10s之后等待失败(线程数小于1或者信号通道允许信号数为0),函数dispatch_semaphore_wait返回非0,继续往下执行
*/
long result = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)));
if (result == 0) {
[marr addObject:@(i)];
}
NSLog(@"%d",i);
// 将信号通道的允许信号数加一,并唤醒其他等待的线程,唤醒成功返回非0 ,唤醒失败返回0,这样信号通道中允许的信号数为1,下一个循环中,只要队列中的线程大于1就可以正常执行了。
dispatch_semaphore_signal(semaphore);
});
}
打印结果太多,截取部分如下
2018-10-09 15:16:19.722482+0800 GCDTest[9209:2227242] 0
2018-10-09 15:16:19.722754+0800 GCDTest[9209:2227240] 1
2018-10-09 15:16:19.722923+0800 GCDTest[9209:2227239] 2
2018-10-09 15:16:19.723054+0800 GCDTest[9209:2227429] 3
2018-10-09 15:16:19.723202+0800 GCDTest[9209:2227430] 4
2018-10-09 15:16:19.723388+0800 GCDTest[9209:2227431] 5
2018-10-09 15:16:19.723513+0800 GCDTest[9209:2227433] 7
2018-10-09 15:16:19.723650+0800 GCDTest[9209:2227432] 6
2018-10-09 15:16:19.723764+0800 GCDTest[9209:2227242] 9
2018-10-09 15:16:19.724041+0800 GCDTest[9209:2227434] 8
2018-10-09 15:16:19.724964+0800 GCDTest[9209:2227435] 10
2018-10-09 15:16:19.725338+0800 GCDTest[9209:2227436] 11
2018-10-09 15:16:19.725754+0800 GCDTest[9209:2227437] 12
注释比较清楚可以仔细看看注释。
根据上面的执行结果和代码分析,我们可以知道以下结果
1.如果注释掉代码
dispatch_semaphore_signal(semaphore)
,那数组marr 中只会有一个对象,因为dispatch_semphore_wait函数只在首次调用时等待成功过;
2.dispatch_semphore_wait 会阻塞队列中的线程。但是不会阻塞主线程,也不会影响for循环,for循环很快就执行完成了,也就是说,队列中在for循环执行完成时,就已经被追加了10000个任务了。只是这些任务受信号管道限制而不能随意执行;
3.任务执行是无序的。
这些都是可以验证的,这里略去不表。
还有个问题,想不明白,就是注释掉dispatch_semaphore_signal(semaphore)后,执行完所有的任务后程序会崩溃。不知道是什么原因,可能跟dispatch_semphore_wait和dispatch_semaphore_signal要成对出现吧。如果有哪位大神知道,还请多多指点
根据这个图片可以看出for循环不会被阻塞,阻塞的是队列中的任务。