iOS - GCD单例、栅栏、信号量
1.单例
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"单例应用");
});
平常我们会用到上面的代码来创建单例。
单例的特点是单例只执行一次
那他底层是如何处理的,让他只执行一次?且他的block哪时候调用的?
带着这个问题,进入单例的底层来看看。
-
dispatch_once底层是
dispatch_once_f
实现的- 参数1:onceToken是一个静态变量,具有唯一性
- 参数2:block
void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
- 进入
dispatch_once_f
源码
内部处理
-
将val(就是外部的onceToken) 转换为
dispatch_once_gate_t
类型l
-
os_atomic_load
获取任务标识符v
-
if (likely(v == DLOCK_ONCE_DONE))
,任务已执行,return- 如果加锁失败了,则走到
_dispatch_once_mark_done_if_quiesced
函数,再次进行存储,将标识符置为DLOCK_ONCE_DONE
- 如果加锁失败了,则走到
-
else
通过_dispatch_once_gate_tryenter
解锁,然后_dispatch_once_callout
执行block
回调
-
-
若此时有任务进入,则进入等待
_dispatch_once_wait
DISPATCH_NOINLINE
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
dispatch_once_gate_t l = (dispatch_once_gate_t)val;
#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
uintptr_t v = os_atomic_load(&l->dgo_once, acquire);//load
if (likely(v == DLOCK_ONCE_DONE)) {//已经执行过了,直接返回
return;
}
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
if (likely(DISPATCH_ONCE_IS_GEN(v))) {
return _dispatch_once_mark_done_if_quiesced(l, v);
}
#endif
#endif
if (_dispatch_once_gate_tryenter(l)) {//尝试进入
return _dispatch_once_callout(l, ctxt, func);
}
return _dispatch_once_wait(l);//无限次等待
}
单例总结:
-
单例只执行一次:dispatch_once 的参数
onceToken
,onceToken是静态变量,具有唯一性
,在底层被封装成了dispatch_once_gate_t
类型的变量l,l主要是用来获取底层原子封装性的关联,即变量v,通过v来查询任务的状态,如果此时v等于DLOCK_ONCE_DONE,说明任务已经处理过一次了,直接return
-
block调用时机:如果此时任务没有执行过,则会在底层通过C++函数的比较,将任务进行加锁,即任务状态置为DLOCK_ONCE_UNLOCK,目的是为了保证当前任务执行的唯一性,防止在其他地方有多次定义。加锁之后进行block回调函数的执行,执行完成后,将当前任务解锁,将当前的任务状态置为DLOCK_ONCE_DONE,在下次进来时,就不会在执行
流程图来自Style_月月
2.栅栏函数
栅栏函数的主要作用:控制队列的执行顺序
(但只能控制同一并发队列的顺序
)
栅栏函数主要有:
-
同步栅栏
dispatch_barrier_sync
(会阻塞线程) -
异步栅栏
dispatch_barrier_async
(不阻塞线程,只阻塞队列,必须是自定义的并发队列)
通俗的讲就是栅栏在两个执行语句中间,隔开
在使用栅栏函数时.使用自定义队列才有意义,如果用的是串行队列或者系统提供的全局并发队列,这个栅栏函数的作用等同于一个同步函数的作用,没有任何意义
3.信号量
信号量的作用一般是用来
使任务同步执行
,类似于互斥锁,用户可以根据需要控制GCD最大并发数
,一般是这样使用的
- dispatch_semaphore_create创建信号量
- dispatch_semaphore_wait上锁
- dispatch_semaphore_signal解锁
//信号量
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(sem);
信号量:其实是可以在不同队列中使用同一个信号量的,
也可以让异步并行队列按顺序执行。
4.调度组
调度组的最直接作用是控制任务执行顺序
常规使用方法:
dispatch_group_create 创建组
dispatch_group_async 进组任务
dispatch_group_notify 进组任务执行完毕通知 dispatch_group_wait 进组任务执行等待时间
//进组和出组一般是成对使用的
dispatch_group_enter 进组
dispatch_group_leave 出组
dispatch_group_enter
进组 ,dispatch_group_leave
出组是成对出现的,否则dispatch_group_notify
不调用,
dispatch_group_async
等同于enter - leave
,其底层的实现就是enter-leave
dispatch_group_async
:
但如果group
的block内部有其他异步并行队列
就不会等待了,会直接执行dispatch_group_notify
而dispatch_group_enter、和dispatch_group_leave则不会有这个问题