栅栏函数
关于栅栏函数,系统提供了两个方法
- dispatch_barrier_async
- dispatch_barrier_sync
dispatch_barrier_sync
和dispatch_barrier_async
区别会不会阻塞当前的线程,要注意,栅栏函数只能控制同一队列。
全局并发队列 :dispatch_get_global_queue
会使栅栏函数失效
栅栏函数使用
同步栅栏函数
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
/* 1.异步函数 */
dispatch_async(concurrentQueue, ^{
NSLog(@"1");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"2");
});
/* 2. 栅栏函数 */
dispatch_barrier_sync(concurrentQueue, ^{
NSLog(@"3");
});
/* 3. 异步函数 */
dispatch_async(concurrentQueue, ^{
NSLog(@"4");
});
// 4
NSLog(@"5");
image.png
这里打印的结果为
12354
,接下来分析下
- 因为这里是
dispatch_barrier_sync
同步栅栏函数,阻塞当前的线程,所以5一定是在3
后面打印 - 栅栏函数是在同一队列的任务,栅栏上方的任务先执行,当上方任务执行完毕再执行栅栏内部任务,最后执行栅栏下方任务
- 所以
1,2
先打印,1,2
的顺序不固定。接下来一定是打印3
- 后面打印
5
,是因为dispatch_async
本身存在耗时操作,4一定在5后面
异步栅栏函数
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
/* 1.异步函数 */
dispatch_async(concurrentQueue, ^{
NSLog(@"1");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"2");
});
/* 2. 栅栏函数 */ //
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"3");
});
/* 3. 异步函数 */
dispatch_async(concurrentQueue, ^{
NSLog(@"4");
});
// 4
NSLog(@"5");
image.png
这里的打印顺序为
51234
,接下来分析下
- 观察打印顺序和
同步栅栏函数
唯一的区别是5
的打印 - 是因为异步栅栏函数不会阻塞当前线程,而
dispatch_async
存在耗时,所以5
先打印,剩下的顺序与同步栅栏函数一致
栅栏函数底层分析
通过libdispatch源码
进入函数dispatch_barrier_sync
->_dispatch_barrier_sync_f
->_dispatch_barrier_sync_f_inline
进入_dispatch_barrier_sync_f_inline
_dispatch_sync_f_slow
_dispatch_sync_recurse
-
_dispatch_lane_barrier_sync_invoke_and_complete
这里有这3个方法我们不知道最终进入哪个,我们通过符号断点来确认
image.png
进入_dispatch_sync_f_slow
image.png
继续跟踪流程,并添加_dispatch_sync_invoke_and_complete_recurse
的符号断点,并进入
image.png
进入_dispatch_sync_complete_recurse
image.png
这里我们发现是个do while
循环,这里思考下为什么这么写? - 栅栏函数起到的的是同步作用,同一队列中,栅栏前的任务没有执行栅栏函数是不会走的
- 所以这里需要
递归处理栅栏函数前面的任务
- 在
_dispatch_sync_complete_recurse
中的递归
中,先判断barrier
是否存在,如果存在则需要先把栅栏前的任务dx_wakeup 全部唤醒
。唤醒成功后才会执行_dispatch_lane_non_barrier_complete
先来查看dx_wakeup
,来查看barrier
什么时候被移出,dx_wakeup
是通过宏定义的函数
#define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)
搜索dq_wakeup
由上图可知
串行队列和并行队列
都走了_dispatch_lane_wakeup
,而全局并发队列
走了_dispatch_root_queue_wakeup
- 串行、并行队列,进入
_dispatch_lane_wakeup
image.png
进入_dispatch_lane_barrier_complete
image.png - 如果是
串行队列
则会进行等待,知道其他任务执行完成,按顺序执行 - 如果是并行队列,调用
_dispatch_lane_drain_non_barriers
将栅栏前的任务按照异步的放心执行
- 全局并发队列
当是全局并发队列的时候,进入_dispatch_root_queue_wakeup
image.png
由上图可知,全局并发队列并没有栅栏函数的相关处理流程,这也是栅栏函数在全局并发队列失效的原因
【问题】为什么全局并发队列中不对栅栏函数进行处理
【答】因为全局并发队列除了被我们使用,系统也在使用,如果添加了栅栏函数,会导致队列运行的阻塞,从而影响系统级的运行,所以栅栏函数也就不适用于全局并发队列。
信号量(dispatch_semaphore_t)
-
dispatch_semaphore_create
创建一个Semaphore
,并初始信号总量 -
dispatch_semaphore_wait
信号量减1
,当信号量小于0
时,就会所在线程发生阻塞,大于等于0
时,正常执行 -
dispatch_semaphore_signal
信号量加1
案例
image.png这里我i们的正常理解应该是先执行
任务1
,但是这里初始化的信号总量为0
,且在任务1中dispatch_semaphore_wait
起到了加锁作用,所以先去执行任务2
,且发出了dispatch_semaphore_signal
解锁信号,再去执行任务`
信号量底层分析
dispatch_semaphore_wait
首先对信号量做
减1
操作,当信号量大于等于0时直接返回,否则进入_dispatch_semaphore_wait_slow
方法image.png
对
timeout
进行判断
- 如果是默认的,会直接跳出
-
DISPATCH_TIME_NOW
,会进行一个超时处理 -
DISPATCH_TIME_FOREVER
会进入_dispatch_sema4_wait
方法
进入_dispatch_sema4_wait
方法
void
_dispatch_sema4_wait(_dispatch_sema4_t *sema)
{
int ret = 0;
do {
ret = sem_wait(sema);
} while (ret == -1 && errno == EINTR);
DISPATCH_SEMAPHORE_VERIFY_RET(ret);
}
我们发现有个do while
方法,并调用sem_wait
,全局搜索sem_wait
并没有搜索出
sem_wait
方法的实现所以
_dispatch_sema4_wait
的do while
就是个死循环,原因就是要让该任务一致处于等待状态
dispatch_semaphore_signal
通过
os_atomic_inc2o
对信号量做+1
操作,如果大于0
直接返回。如果
加过一次后仍小于0
,则会抛出异常Unbalanced call to dispatch_semaphore_signal()
并调用_dispatch_semaphore_signal_slow
方法,见下图:image.png
这里会开启一个循环,对信号量加一操作,知道满足条件位置
【总结】信号来那个在实际开发中的作用
- 保持线程同步,将异步执行的任务,转换成同步操作
- 保证线程安全,为线程加锁(自旋锁)
调度组
dispatch_group_create
创建组
dispatch_group_async
进组任务
dispatch_group_notify
进组任务执行完毕通知
dispatch_group_wait
进组任务执行等待时间
dispatch_group_enter
进组
dispatch_group_leave
出组
两者搭配使用
案例实现1
image.png我们在异步线程中加了个
sleep(2)
,这个时候在主线程
打印为空数组
,但是在我们的dispatch_group_notify
中是能够打印出数组的内容。是因为在相同组中的任务,都执行完毕后会走dispatch_group_notify
该方法。【注意】
dispatch_group_enter
和dispatch_group_leave
要成对出现,并且dispatch_group_enter
在执行任务前,dispatch_group_leave
任务执行完成后调用,否则顺序错误会报错
案例实现2
image.png这里直接将任务放在
dispatch_group_async
,最终结果和上述案例相同,其实dispatch_group_async
就是底层封装了dispatch_group_enter 和dispatch_group_leave
调度组底层分析
dispatch_group_create
调用
_dispatch_group_create_with_count
并将信号量默认传0
image.png
通过
os_atomic_store2o
进行保存
dispatch_group_enter
默认信号量为0 ,所以信号量
减1
,由0
变-1
,old_bits等于-1
dispatch_group_leave
信号量
加1
,此时的newState等于0
,oldState等于-1
#define DISPATCH_GROUP_VALUE_MASK 0x00000000fffffffcULL
#define DISPATCH_GROUP_VALUE_1 DISPATCH_GROUP_VALUE_MASK
old_state & DISPATCH_GROUP_VALUE_MASK
等于0
,即old_value等于0
也就是 old_value 与DISPATCH_GROUP_VALUE_1
不会相等,最终调用if中的 return _dispatch_group_wake
,_dispatch_group_wake
也就是去唤醒dispatch_group_notify
dispatch_group_notify
这里判断
old_state == 0
就去唤醒函数的执行流程,在上一步已经分析出old_state = 0
所以这里也就解释了dispatch_group_enter和dispatch_group_leave
为什么要配合起来使用
网友评论