栅栏函数
关于栅栏函数,系统提供了两个方法
- 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");

这里打印的结果为
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");

这里的打印顺序为
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
案例

这里我i们的正常理解应该是先执行
任务1,但是这里初始化的信号总量为0,且在任务1中dispatch_semaphore_wait起到了加锁作用,所以先去执行任务2,且发出了dispatch_semaphore_signal解锁信号,再去执行任务`
信号量底层分析
dispatch_semaphore_wait

首先对信号量做
减1操作,当信号量大于等于0时直接返回,否则进入_dispatch_semaphore_wait_slow方法
对
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方法,见下图:
这里会开启一个循环,对信号量加一操作,知道满足条件位置
【总结】信号来那个在实际开发中的作用
- 保持线程同步,将异步执行的任务,转换成同步操作
- 保证线程安全,为线程加锁(自旋锁)
调度组
dispatch_group_create创建组
dispatch_group_async进组任务
dispatch_group_notify进组任务执行完毕通知
dispatch_group_wait 进组任务执行等待时间
dispatch_group_enter 进组
dispatch_group_leave 出组
两者搭配使用
案例实现1

我们在异步线程中加了个
sleep(2),这个时候在主线程打印为空数组,但是在我们的dispatch_group_notify中是能够打印出数组的内容。是因为在相同组中的任务,都执行完毕后会走dispatch_group_notify该方法。【注意】
dispatch_group_enter和dispatch_group_leave要成对出现,并且dispatch_group_enter在执行任务前,dispatch_group_leave任务执行完成后调用,否则顺序错误会报错
案例实现2

这里直接将任务放在
dispatch_group_async,最终结果和上述案例相同,其实dispatch_group_async就是底层封装了dispatch_group_enter 和dispatch_group_leave
调度组底层分析
dispatch_group_create

调用
_dispatch_group_create_with_count并将信号量默认传0
通过
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为什么要配合起来使用






