本文是队列创建、同步/异步函数、单例、信号量以及调度组的底层原理分析
队列创建
在上一篇文章GCD 之 函数与队列中,我们理解了队列与函数,知道队列的创建时通过GCD中的dispatch_queue_create方法,下面我们在libdispatch.dylib去探索队列是如何创建的(下载链接)
底层源码分析
- 在源码中搜索
dispatch_queue_create
dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
return _dispatch_lane_create_with_target(label, attr, DISPATCH_TARGET_QUEUE_DEFAULT, true);
}
- 进入
_dispatch_lane_create_with_target(
DISPATCH_NOINLINE
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq, bool legacy)
{
// dqai 创建 -
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
//第一步:规范化参数,例如qos, overcommit, tq
...
//拼接队列名称
const void *vtable;
dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
if (dqai.dqai_concurrent) { //vtable表示类的类型
// OS_dispatch_queue_concurrent
vtable = DISPATCH_VTABLE(queue_concurrent);
} else {
vtable = DISPATCH_VTABLE(queue_serial);
}
....
//创建队列,并初始化
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s)); // alloc
//根据dqai.dqai_concurrent的值,就能判断队列 是 串行 还是并发
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0)); // init
//设置队列label标识符
dq->dq_label = label;//label赋值
dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos, dqai.dqai_relpri);//优先级处理
...
//类似于类与元类的绑定,不是直接的继承关系,而是类似于模型与模板的关系
dq->do_targetq = tq;
_dispatch_object_debug(dq, "%s", __func__);
return _dispatch_trace_queue_create(dq)._dq;//研究dq
}
_dispatch_lane_create_with_target 分析
-
【第一步】通过
_dispatch_queue_attr_to_info方法传入dqa(即队列类型,串行、并发等)创建dispatch_queue_attr_info_t类型的对象dqai,用于存储队列的相关属性信息 -
【第二步】设置队列相关联的属性,例如服务质量qos等
-
【第三步】通过
DISPATCH_VTABLE拼接队列名称,即vtable,其中DISPATCH_VTABLE是宏定义,如下所示,所以队列的类型是通过OS_dispatch_+队列类型queue_concurrent拼接而成的-
串行队列类型:OS_dispatch_queue_serial,验证如下 -
并发队列类型:OS_dispatch_queue_concurrent,验证如下
-
#define DISPATCH_VTABLE(name) DISPATCH_OBJC_CLASS(name)
👇
#define DISPATCH_OBJC_CLASS(name) (&DISPATCH_CLASS_SYMBOL(name))
👇
#define DISPATCH_CLASS(name) OS_dispatch_##name
-
【第四步】通过
alloc+init初始化队列,即dq,其中在_dispatch_queue_init传参中根据dqai.dqai_concurrent的布尔值,就能判断队列 是串行还是并发,而vtable表示队列的类型,说明队列也是对象-
进入
_dispatch_object_alloc -> _os_object_alloc_realized方法中设置了isa的指向,从这里可以验证队列也是对象的说法 -
进入
_dispatch_queue_init方法,队列类型是dispatch_queue_t,并设置队列的相关属性
-
-
【第五步】通过
_dispatch_trace_queue_create对创建的队列进行处理,其中_dispatch_trace_queue_create是_dispatch_introspection_queue_create封装的宏定义,最后会返回处理过的_dq-
进入
_dispatch_introspection_queue_create_hook -> dispatch_introspection_queue_get_info -> _dispatch_introspection_lane_get_info中可以看出,与我们自定义的类还是有所区别的,创建队列在底层的实现是通过模板创建的
-
总结
-
队列创建方法
dispatch_queue_create中的参数二(即队列类型),决定了下层中max & 1(用于区分是 串行 还是 并发),其中1表示串行 -
queue也是一个对象,也需要底层通过alloc + init 创建,并且在alloc中也有一个class,这个class是通过宏定义拼接而成,并且同时会指定isa的指向 -
创建队列在底层的处理是通过模板创建的,其类型是dispatch_introspection_queue_s结构体
dispatch_queue_create底层分析流程如下图所示
函数 底层原理分析
主要是分析 异步函数dispatch_async 和 同步函数dispatch_sync
异步函数
进入dispatch_async的源码实现,主要分析两个函数
-
_dispatch_continuation_init:任务包装函数 -
_dispatch_continuation_async:并发处理函数
void
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)//work 任务
{
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME;
dispatch_qos_t qos;
// 任务包装器(work在这里才有使用) - 接受work - 保存work - 并函数式编程
// 保存 block
qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
//并发处理
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
_dispatch_continuation_init 任务包装器
进入_dispatch_continuation_init源码实现,主要是包装任务,并设置线程的回程函数,相当于初始化
DISPATCH_ALWAYS_INLINE
static inline dispatch_qos_t
_dispatch_continuation_init(dispatch_continuation_t dc,
dispatch_queue_class_t dqu, dispatch_block_t work,
dispatch_block_flags_t flags, uintptr_t dc_flags)
{
void *ctxt = _dispatch_Block_copy(work);//拷贝任务
dc_flags |= DC_FLAG_BLOCK | DC_FLAG_ALLOCATED;
if (unlikely(_dispatch_block_has_private_data(work))) {
dc->dc_flags = dc_flags;
dc->dc_ctxt = ctxt;//赋值
// will initialize all fields but requires dc_flags & dc_ctxt to be set
return _dispatch_continuation_init_slow(dc, dqu, flags);
}
dispatch_function_t func = _dispatch_Block_invoke(work);//封装work - 异步回调
if (dc_flags & DC_FLAG_CONSUME) {
func = _dispatch_call_block_and_release;//回调函数赋值 - 同步回调
}
return _dispatch_continuation_init_f(dc, dqu, ctxt, func, flags, dc_flags);
}
主要有以下几步
-
通过
_dispatch_Block_copy拷贝任务 -
通过
_dispatch_Block_invoke封装任务,即异步回调 -
如果是
同步的,则回调函数赋值为_dispatch_call_block_and_release -
通过
_dispatch_continuation_init_f方法将回调函数赋值,即f就是func,将其保存在属性中
_dispatch_continuation_async 并发处理
这个函数中,主要是执行block回调
- 进入
_dispatch_continuation_async的源码实现
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_continuation_async(dispatch_queue_class_t dqu,
dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags)
{
#if DISPATCH_INTROSPECTION
if (!(dc_flags & DC_FLAG_NO_INTROSPECTION)) {
_dispatch_trace_item_push(dqu, dc);//跟踪日志
}
#else
(void)dc_flags;
#endif
return dx_push(dqu._dq, dc, qos);//与dx_invoke一样,都是宏
}
- 其中的关键代码是
dx_push(dqu._dq, dc, qos),dx_push是宏定义,如下所示
#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
-
而其中的
dq_push需要根据队列的类型,执行不同的函数
符号断点调试执行函数
- 运行demo,通过
符号断点,来判断执行的是哪个函数,由于是并发队列,通过增加_dispatch_lane_concurrent_push符号断点,看看是否会走到这里
dispatch_queue_t conque = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(conque, ^{
NSLog(@"异步函数");
});
-
运行发现,走的确实是
_dispatch_lane_concurrent_push -
进入
_dispatch_lane_concurrent_push源码,发现有两步,继续通过符号断点_dispatch_continuation_redirect_push和_dispatch_lane_push调试,发现走的是_dispatch_continuation_redirect_push -
进入
_dispatch_continuation_redirect_push源码,发现又走到了dx_push,即递归了,综合前面队列创建时可知,队列也是一个对象,有父类、根类,所以会递归执行到根类的方法 -
接下来,通过根类的
_dispatch_root_queue_push符号断点,来验证猜想是否正确,从运行结果看出,完全是正确的 -
进入
_dispatch_root_queue_push -> _dispatch_root_queue_push_inline ->_dispatch_root_queue_poke -> _dispatch_root_queue_poke_slow源码,经过符号断点验证,确实是走的这里,查看该方法的源码实现,主要有两步操作-
通过
_dispatch_root_queues_init方法注册回调 -
通过do-while循环创建线程,使用
pthread_create方法
-
DISPATCH_NOINLINE
static void
_dispatch_root_queue_poke_slow(dispatch_queue_global_t dq, int n, int floor)
{
int remaining = n;
int r = ENOSYS;
_dispatch_root_queues_init();//重点
...
//do-while循环创建线程
do {
_dispatch_retain(dq); // released in _dispatch_worker_thread
while ((r = pthread_create(pthr, attr, _dispatch_worker_thread, dq))) {
if (r != EAGAIN) {
(void)dispatch_assume_zero(r);
}
_dispatch_temporary_resource_shortage();
}
} while (--remaining);
...
}
_dispatch_root_queues_init
- 进入
_dispatch_root_queues_init源码实现,发现是一个dispatch_once_f单例(请查看后续单例的底层分析们,这里不作说明),其中传入的func是_dispatch_root_queues_init_once。
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_root_queues_init(void)
{
dispatch_once_f(&_dispatch_root_queues_pred, NULL, _dispatch_root_queues_init_once);
}
-
进入
_dispatch_root_queues_init_once的源码,其内部不同事务的调用句柄都是_dispatch_worker_thread2
其block回调执行的调用路径为:_dispatch_root_queues_init_once ->_dispatch_worker_thread2 -> _dispatch_root_queue_drain -> _dispatch_root_queue_drain -> _dispatch_continuation_pop_inline -> _dispatch_continuation_invoke_inline -> _dispatch_client_callout -> dispatch_call_block_and_release
这个路径可以通过断点,bt打印堆栈信息得出
说明
在这里需要说明一点的是,单例的block回调和异步函数的block回调是不同的
-
单例中,block回调中的
func是_dispatch_Block_invoke(block) -
而
异步函数中,block回调中的func是dispatch_call_block_and_release
总结
所以,综上所述,异步函数的底层分析如下
-
【准备工作】:首先,将异步任务拷贝并封装,并设置回调函数
func -
【block回调】:底层通过
dx_push递归,会重定向到根队列,然后通过pthread_creat创建线程,最后通过dx_invoke执行block回调(注意dx_push和dx_invoke是成对的)
异步函数的底层分析流程如图所示
同步函数
进入dispatch_sync源码实现,其底层的实现是通过栅栏函数实现的(栅栏函数的底层分析见后文)
DISPATCH_NOINLINE
void
dispatch_sync(dispatch_queue_t dq, dispatch_block_t work)
{
uintptr_t dc_flags = DC_FLAG_BLOCK;
if (unlikely(_dispatch_block_has_private_data(work))) {
return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
}
_dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}
-
进入
_dispatch_sync_f源码 -
查看
_dispatch_sync_f_inline源码,其中width = 1表示是串行队列,其中有两个重点:-
栅栏:
_dispatch_barrier_sync_f(可以通过后文的栅栏函数底层分析解释),可以得出同步函数的底层实现其实是同步栅栏函数 -
死锁:
_dispatch_sync_f_slow,如果存在相互等待的情况,就会造成死锁
-
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func, uintptr_t dc_flags)
{
if (likely(dq->dq_width == 1)) {//表示是串行队列
return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);//栅栏
}
if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
}
dispatch_lane_t dl = upcast(dq)._dl;
// Global concurrent queues and queues bound to non-dispatch threads
// always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
if (unlikely(!_dispatch_queue_try_reserve_sync_width(dl))) {
return _dispatch_sync_f_slow(dl, ctxt, func, 0, dl, dc_flags);//死锁
}
if (unlikely(dq->do_targetq->do_targetq)) {
return _dispatch_sync_recurse(dl, ctxt, func, dc_flags);
}
_dispatch_introspection_sync_begin(dl);//处理当前信息
_dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(
_dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));//block执行并释放
}
_dispatch_sync_f_slow 死锁
-
进入
_dispatch_sync_f_slow,当前的主队列是挂起、阻塞的 -
往一个队列中 加入任务,会push加入主队列,进入
_dispatch_trace_item_push -
进入
__DISPATCH_WAIT_FOR_QUEUE__,判断dq是否为正在等待的队列,然后给出一个状态state,然后将dq的状态和当前任务依赖的队列进行匹配 -
进入
_dq_state_drain_locked_by -> _dispatch_lock_is_locked_by源码
DISPATCH_ALWAYS_INLINE
static inline bool
_dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid)
{
// equivalent to _dispatch_lock_owner(lock_value) == tid
//异或操作:相同为0,不同为1,如果相同,则为0,0 &任何数都为0
//即判断 当前要等待的任务 和 正在执行的任务是否一样,通俗的解释就是 执行和等待的是否在同一队列
return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}
如果当前等待的和正在执行的是同一个队列,即判断线程ID是否相乘,如果相等,则会造成死锁
同步函数 + 并发队列 顺序执行的原因
在_dispatch_sync_invoke_and_complete -> _dispatch_sync_function_invoke_inline源码中,主要有三个步骤:
- 将任务压入队列:
_dispatch_thread_frame_push - 执行任务的block回调:
_dispatch_client_callout - 将任务出队:
_dispatch_thread_frame_pop
从实现中可以看出,是先将任务push队列中,然后执行block回调,在将任务pop,所以任务是顺序执行的。
总结
同步函数的底层实现如下:
-
同步函数的底层实现实际是同步栅栏函数 -
同步函数中如果
当前正在执行的队列和等待的是同一个队列,形成相互等待的局面,则会造成死锁
所以,综上所述,同步函数的底层实现流程如图所示
单例
在日常开发中,我们一般使用GCD的dispatch_once来创建单例,如下所示
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"单例应用");
});
首先对于单例,我们需要了解两点
【执行一次的原因】单例的流程只执行一次,底层是如何控制的,即为什么只能执行一次?【block调用时机】单例的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静态变量,而func是_dispatch_Block_invoke(block),其中单例的底层主要分为以下几步-
将
val,也就是静态变量转换为dispatch_once_gate_t类型的变量l -
通过
os_atomic_load获取此时的任务的标识符v-
如果
v等于DLOCK_ONCE_DONE,表示任务已经执行过了,直接return -
如果 任务执行后,
加锁失败了,则走到_dispatch_once_mark_done_if_quiesced函数,再次进行存储,将标识符置为DLOCK_ONCE_DONE -
反之,则通过
_dispatch_once_gate_tryenter尝试进入任务,即解锁,然后执行_dispatch_once_callout执行block回调
-
-
如果此时有任务正在执行,再次进来一个任务2,则通过
_dispatch_once_wait函数让任务2进入无限次等待
-
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_gate_tryenter 解锁
查看其源码,主要是通过底层os_atomic_cmpxchg方法进行对比,如果比较没有问题,则进行加锁,即任务的标识符置为DLOCK_ONCE_UNLOCKED
DISPATCH_ALWAYS_INLINE
static inline bool
_dispatch_once_gate_tryenter(dispatch_once_gate_t l)
{
return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
(uintptr_t)_dispatch_lock_value_for_self(), relaxed);//首先对比,然后进行改变
}
_dispatch_once_callout 回调
进入_dispatch_once_callout源码,主要就两步
-
_dispatch_client_callout:block回调执行 -
_dispatch_once_gate_broadcast:进行广播
DISPATCH_NOINLINE
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
dispatch_function_t func)
{
_dispatch_client_callout(ctxt, func);//block调用执行
_dispatch_once_gate_broadcast(l);//进行广播:告诉别人有了归属,不要找我了
- 进入
_dispatch_client_callout源码,主要就是执行block回调,其中的f等于_dispatch_Block_invoke(block),即异步回调
#undef _dispatch_client_callout
void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
@try {
return f(ctxt);
}
@catch (...) {
objc_terminate();
}
}
- 进入
_dispatch_once_gate_broadcast -> _dispatch_once_mark_done源码,主要就是给dgo->dgo_once一个值,然后将任务的标识符为DLOCK_ONCE_DONE,即解锁
DISPATCH_ALWAYS_INLINE
static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
//如果不相同,直接改为相同,然后上锁 -- DLOCK_ONCE_DONE
return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}
总结
针对单例的底层实现,主要说明如下:
-
【单例只执行一次的原理】:GCD单例中,有两个重要参数,
onceToken和block,其中onceToken是静态变量,具有唯一性,在底层被封装成了dispatch_once_gate_t类型的变量l,l主要是用来获取底层原子封装性的关联,即变量v,通过v来查询任务的状态,如果此时v等于DLOCK_ONCE_DONE,说明任务已经处理过一次了,直接return -
【block调用时机】:如果此时任务没有执行过,则会在底层通过C++函数的比较,将
任务进行加锁,即任务状态置为DLOCK_ONCE_UNLOCK,目的是为了保证当前任务执行的唯一性,防止在其他地方有多次定义。加锁之后进行block回调函数的执行,执行完成后,将当前任务解锁,将当前的任务状态置为DLOCK_ONCE_DONE,在下次进来时,就不会在执行,会直接返回 -
【多线程影响】:如果在当前任务执行期间,有其他任务进来,会进入无限次等待,原因是当前任务已经获取了锁,进行了加锁,其他任务是无法获取锁的
单例的底层流程分析如下如所示
栅栏函数
GCD中常用的栅栏函数,主要有两种
-
同步栅栏函数dispatch_barrier_sync(在主线程中执行):前面的任务执行完毕才会来到这里,但是同步栅栏函数会堵塞线程,影响后面的任务执行 -
异步栅栏函数dispatch_barrier_async:前面的任务执行完毕才会来到这里
栅栏函数最直接的作用就是 控制任务执行顺序,使同步执行。
同时,栅栏函数需要注意一下几点
- 栅栏函数
只能控制同一并发队列
同步栅栏添加进入队列的时候,当前线程会被锁死,直到同步栅栏之前的任务和同步栅栏任务本身执行完毕时,当前线程才会打开然后继续执行下一句代码。
在使用栅栏函数时.使用自定义队列才有意义,如果用的是串行队列或者系统提供的全局并发队列,这个栅栏函数的作用等同于一个同步函数的作用,没有任何意义
代码调试
总共有4个任务,其中前2个任务有依赖关系,即任务1执行完,执行任务2,此时可以使用栅栏函数
-
异步栅栏函数 不会阻塞主线程 ,异步
堵塞 的是队列 -
同步栅栏函数 会
堵塞主线程,同步 堵塞 是当前的线程
总结
-
异步栅栏函数阻塞的是队列,而且必须是自定义的并发队列,不影响主线程任务的执行 -
同步栅栏函数阻塞的是线程,且是主线程,会影响主线程其他任务的执行
使用场景
栅栏函数除了用于任务有依赖关系时,同时还可以用于数据安全
像下面这样操作,会崩溃
崩溃的原因是:数据在不断的retain 和 release,在数据还没有retain完毕时,已经开始了release,相当于加了一个空数据,进行release
修改
- 通过加栅栏函数
- (void)use041{
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i<100000; i++) {
dispatch_async(concurrentQueue, ^{
dispatch_barrier_async(concurrentQueue, ^{
[array addObject:[NSString stringWithFormat:@"%d", i]];
});
});
}
}
- 使用互斥锁
@synchronized (self) {}
- (void)use041{
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i<100000; i++) {
dispatch_async(concurrentQueue, ^{
@synchronized (self) {
[array addObject:[NSString stringWithFormat:@"%d", i]];
};
});
}
}
注意
-
如果栅栏函数中使用
全局队列, 运行会崩溃,原因是系统也在用全局并发队列,使用栅栏同时会拦截系统的,所以会崩溃 -
如果将
自定义并发队列改为串行队列,即serial ,串行队列本身就是有序同步此时加栅栏,会浪费性能 -
栅栏函数
只会阻塞一次
异步栅栏函数 底层分析
进入dispatch_barrier_async源码实现,其底层的实现与dispatch_async类似,这里就不再做分析了,有兴趣的可以自行探索下
#ifdef __BLOCKS__
void
dispatch_barrier_async(dispatch_queue_t dq, dispatch_block_t work)
{
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_BARRIER;
dispatch_qos_t qos;
qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
_dispatch_continuation_async(dq, dc, qos, dc_flags);
}
#endif
同步栅栏函数 底层分析
进入dispatch_barrier_sync源码,实现如下
void
dispatch_barrier_sync(dispatch_queue_t dq, dispatch_block_t work)
{
uintptr_t dc_flags = DC_FLAG_BARRIER | DC_FLAG_BLOCK;
if (unlikely(_dispatch_block_has_private_data(work))) {
return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
}
_dispatch_barrier_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}
_dispatch_barrier_sync_f_inline
进入_dispatch_barrier_sync_f -> _dispatch_barrier_sync_f_inline源码
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_barrier_sync_f_inline(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func, uintptr_t dc_flags)
{
dispatch_tid tid = _dispatch_tid_self();//获取线程的id,即线程的唯一标识
...
//判断线程状态,需不需要等待,是否回收
if (unlikely(!_dispatch_queue_try_acquire_barrier_sync(dl, tid))) {//栅栏函数也会死锁
return _dispatch_sync_f_slow(dl, ctxt, func, DC_FLAG_BARRIER, dl,//没有回收
DC_FLAG_BARRIER | dc_flags);
}
//验证target是否存在,如果存在,加入栅栏函数的递归查找 是否等待
if (unlikely(dl->do_targetq->do_targetq)) {
return _dispatch_sync_recurse(dl, ctxt, func,
DC_FLAG_BARRIER | dc_flags);
}
_dispatch_introspection_sync_begin(dl);
_dispatch_lane_barrier_sync_invoke_and_complete(dl, ctxt, func
DISPATCH_TRACE_ARG(_dispatch_trace_item_sync_push_pop(
dq, ctxt, func, dc_flags | DC_FLAG_BARRIER)));//执行
}
源码主要有分为以下几部分
-
通过
_dispatch_tid_self获取线程ID -
通过
_dispatch_queue_try_acquire_barrier_sync判断线程状态-
进入
_dispatch_queue_try_acquire_barrier_sync_and_suspend,在这里进行释放
-
-
通过
_dispatch_sync_recurse递归查找栅栏函数的target -
通过
_dispatch_introspection_sync_begin对向前信息进行处理 -
通过
_dispatch_lane_barrier_sync_invoke_and_complete执行block并释放
信号量
信号量的作用一般是用来使任务同步执行,类似于互斥锁,用户可以根据需要控制GCD最大并发数,一般是这样使用的
//信号量
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(sem);
下面我们来分析其底层原理
dispatch_semaphore_create 创建
该函数的底层实现如下,主要是初始化信号量,并设置GCD的最大并发数,其最大并发数必须大于0
dispatch_semaphore_t
dispatch_semaphore_create(long value)
{
dispatch_semaphore_t dsema;
// If the internal value is negative, then the absolute of the value is
// equal to the number of waiting threads. Therefore it is bogus to
// initialize the semaphore with a negative value.
if (value < 0) {
return DISPATCH_BAD_INPUT;
}
dsema = _dispatch_object_alloc(DISPATCH_VTABLE(semaphore),
sizeof(struct dispatch_semaphore_s));
dsema->do_next = DISPATCH_OBJECT_LISTLESS;
dsema->do_targetq = _dispatch_get_default_queue(false);
dsema->dsema_value = value;
_dispatch_sema4_init(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
dsema->dsema_orig = value;
return dsema;
}
dispatch_semaphore_wait 加锁
该函数的源码实现如下,其主要作用是对信号量dsema通过os_atomic_dec2o进行了--操作,其内部是执行的C++的atomic_fetch_sub_explicit方法
-
如果value 大于等于0,表示操作无效,即
执行成功 -
如果value 等于
LONG_MIN,系统会抛出一个crash -
如果value 小于0,则进入
长等待
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
// dsema_value 进行 -- 操作
long value = os_atomic_dec2o(dsema, dsema_value, acquire);
if (likely(value >= 0)) {//表示执行操作无效,即执行成功
return 0;
}
return _dispatch_semaphore_wait_slow(dsema, timeout);//长等待
}
其中os_atomic_dec2o的宏定义转换如下
os_atomic_inc2o(p, f, m)
👇
os_atomic_sub2o(p, f, 1, m)
👇
_os_atomic_c11_op((p), (v), m, sub, -)
👇
_os_atomic_c11_op((p), (v), m, add, +)
👇
({ _os_atomic_basetypeof(p) _v = (v), _r = \
atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), _v, \
memory_order_##m); (__typeof__(_r))(_r op _v); })
将具体的值代入为
os_atomic_dec2o(dsema, dsema_value, acquire);
os_atomic_sub2o(dsema, dsema_value, 1, m)
os_atomic_sub(dsema->dsema_value, 1, m)
_os_atomic_c11_op(dsema->dsema_value, 1, m, sub, -)
_r = atomic_fetch_sub_explicit(dsema->dsema_value, 1),
等价于 dsema->dsema_value - 1
-
进入
_dispatch_semaphore_wait_slow的源码实现,当value小于0时,根据等待事件timeout做出不同操作
dispatch_semaphore_signal 解锁
该函数的源码实现如下,其核心也是通过os_atomic_inc2o函数对value进行了++操作,os_atomic_inc2o内部是通过C++的atomic_fetch_add_explicit
-
如果value 大于 0,表示操作无效,即
执行成功 -
如果value 等于0,则进入
长等待
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
//signal 对 value是 ++
long value = os_atomic_inc2o(dsema, dsema_value, release);
if (likely(value > 0)) {//返回0,表示当前的执行操作无效,相当于执行成功
return 0;
}
if (unlikely(value == LONG_MIN)) {
DISPATCH_CLIENT_CRASH(value,
"Unbalanced call to dispatch_semaphore_signal()");
}
return _dispatch_semaphore_signal_slow(dsema);//进入长等待
}
其中os_atomic_dec2o的宏定义转换如下
os_atomic_inc2o(p, f, m)
👇
os_atomic_add2o(p, f, 1, m)
👇
os_atomic_add(&(p)->f, (v), m)
👇
_os_atomic_c11_op((p), (v), m, add, +)
👇
({ _os_atomic_basetypeof(p) _v = (v), _r = \
atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), _v, \
memory_order_##m); (__typeof__(_r))(_r op _v); })
将具体的值代入为
os_atomic_inc2o(dsema, dsema_value, release);
os_atomic_add2o(dsema, dsema_value, 1, m)
os_atomic_add(&(dsema)->dsema_value, (1), m)
_os_atomic_c11_op((dsema->dsema_value), (1), m, add, +)
_r = atomic_fetch_add_explicit(dsema->dsema_value, 1),
等价于 dsema->dsema_value + 1
总结
-
dispatch_semaphore_create主要就是初始化限号量 -
dispatch_semaphore_wait是对信号量的value进行--,即加锁操作 -
dispatch_semaphore_signal是对信号量的value进行++,即解锁操作
所以,综上所述,信号量相关函数的底层操作如图所示