欢迎阅读iOS底层系列(建议按顺序)
1.写在前面
本文旨在通过分析GCD相关的底层实现,充分掌握上层API的使用技巧。
2.初步分析
通常使用越是方便的API,往往越不会去窥探它的原理,容易形成拿来即用的习惯,而GCD就像是这样的存在。
那么分析GCD的底层源码的思路是什么?应该从何看起?
———————————————————————————————————————————————————
还记得GCD的核心思想嘛:
将任务添加到队列,并且指定执行任务的函数
分析GCD的底层源码就是紧紧围绕这个思想展开:
- 队列是怎么被创建的
- 同步异步函数的区别
- 任务是何时被执行的
3.底层初探
研究源码,首先需要找到源码所在的库。
GCD的底层相对Objc来说是晦涩难懂的,因为它的分支较多,宏定义较多,命名较长,系统给出的注释较少,且涉及大量内核级的操作,所以分析GCD不会逐行逐句的解释,只会挑选其中的关键部分。
———————————————————————————————————————————————————
底层代码都来自苹果的开源库
我的github有部分注解,后续会陆续更新
3.1 队列的创建
探索的重点在于:
- 队列是怎么被创建的
- 串行和异步是怎么被区分的
————————————————————————————————————————————————————————————————————————————
队列的创建是使用dispatch_queue_create是我们知道的,那么可以在项目中下个dispatch_queue_create符号断点。
运行后显示:
从中可知,dispatch_queue_create是在libdispatch.dylib库中,并且马上调用_dispatch_lane_create_with_target。
苹果底层函数常使用"_"前缀,起到上层函数改变,底层也能快速适配的思想。
在libdispatch中尝试搜索_dispatch_lane_create_with_target,
只有三个结果,并且第一个就是函数实现。
- ①
dqai的初始化
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
在创建队列时,我们指定的队列类型就是dqa(串行或并发),传入的dqa通过_dispatch_queue_attr_to_info函数得到类型为dispatch_queue_attr_info_t的dqai,
dispatch_queue_attr_info_t采用位域的存储方式,其结构体如下:
typedef struct dispatch_queue_attr_info_s {
dispatch_qos_t dqai_qos : 8;
int dqai_relpri : 8;
uint16_t dqai_overcommit:2;
uint16_t dqai_autorelease_frequency:2;
uint16_t dqai_concurrent:1;
uint16_t dqai_inactive:1;
} dispatch_queue_attr_info_t;
,而_dispatch_queue_attr_to_info的函数实现如下
1.1 初始化了空的结构体,如果dqa是为空,则直接返回空的结构体。
这句代码的意思:当为串行队列的(串行传NULL)时候,dqai为空结构体,并返回。
1.2 当dqa不为空时,对结构体内的位域赋值,最后返回dqai。
这句代码的意思:当为并发队列的(并发为非NULL)时候,对结构体内的位域赋值,并返回。
串行和并发的区别在第一行代码就被区分,返回的dqai的状态就是两者后续判断的分水岭。
- ②
overcommit的赋值
overcommit是用于后续计算模板数组下标的值。
overcommit = dqai.dqai_concurrent ?
_dispatch_queue_attr_overcommit_disabled :
_dispatch_queue_attr_overcommit_enabled;
根据dqai.dqai_concurrent是否存在,决定overcommit的值。串行队列的dqai为空,所以
| 队列类型 | overcommit |
|---|---|
| 串行 | _dispatch_queue_attr_overcommit_enabled |
| 并发 | _dispatch_queue_attr_overcommit_disabled |
- ③ 初始化
tq(要执行block块的目标队列)
tq = _dispatch_get_root_queue(
qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos,
overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq;
其中
计算方式:
qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos,
overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq;
相关的宏定义:
#define DISPATCH_QOS_UNSPECIFIED ((dispatch_qos_t)0)
#define DISPATCH_QOS_DEFAULT ((dispatch_qos_t)4)
typedef enum {
_dispatch_queue_attr_overcommit_unspecified = 0,
_dispatch_queue_attr_overcommit_enabled,
_dispatch_queue_attr_overcommit_disabled,
} _dispatch_queue_attr_overcommit_t;
当qos为DISPATCH_QOS_UNSPECIFIED未指明时,设置为默认DISPATCH_QOS_DEFAULT,否则为设定的qos,因为创建队列时,我们一般不会指定优先级,所以qos为DISPATCH_QOS_DEFAULT,等于4;
overcommit上一步赋值过,是个枚举值,串行为1(_dispatch_queue_attr_overcommit_enabled),并发为2(_dispatch_queue_attr_overcommit_disabled)。
然后把值传入_dispatch_get_root_queue函数进行计算
我们可以很快速的算出2 * (qos - 1) + overcommit的值:
| 队列类型 | 2 * (qos - 1) + overcommit |
|---|---|
| 串行 | 7 |
| 并发 | 8 |
最终的值已经得到,先做个标记,后面在回头分析这个值的意义。
- ④ 设置
vtable
并发为queue_concurrent,串行为queue_serial,vtable是后续初始化队列时用来拼接字符串的标识符。
#define DISPATCH_CLASS_SYMBOL(name) _dispatch_##name##_vtable
在GCD中很多宏定义使用##参数##,这个意思其实就是字符串拼接。
- ⑤ 分配空间并初始化队列
1.1 先用_dispatch_object_alloc根据串行还是并发使用vtable先alloc出对应类的空间
搜索底层代码有个小技巧,直接搜索_dispatch_object_alloc关联较多,可以根据它的参数类型进行搜索优化。因为第一个参数是const void *类型,所以可以直接搜_dispatch_object_alloc(const void *缩小范围。
_dispatch_object_alloc函数一路跟下去会来到
static inline id _os_objc_alloc(Class cls, size_t size){
id obj;
size -= sizeof(((struct _os_object_s *)NULL)->os_obj_isa);
while (unlikely(!(obj = class_createInstance(cls, size)))) {
_dispatch_temporary_resource_shortage();
}
return obj;
}
可以看到,vtable拼接之后最终就是转成对应的cls。
objc的源码中,我们知道存在objc_object,这里出现了dispatch_object又是什么?
搜索dispatch_object,
看到dispatch_object_t类型,继续搜索dispatch_object_t:
typedef union {
struct _os_object_s *_os_obj;
struct dispatch_object_s *_do;
struct dispatch_queue_s *_dq;
struct dispatch_queue_attr_s *_dqa;
struct dispatch_group_s *_dg;
struct dispatch_source_s *_ds;
struct dispatch_mach_s *_dm;
struct dispatch_mach_msg_s *_dmsg;
struct dispatch_semaphore_s *_dsema;
struct dispatch_data_s *_ddata;
struct dispatch_io_s *_dchannel;
} dispatch_object_t DISPATCH_TRANSPARENT_UNION;
发现dispatch_object_t是个联合体,联合体的形式在isa中也有出现过。
里面包括很多常见的GCD类型,比如dispatch_group_s,dispatch_source_s,dispatch_semaphore_s,dispatch_queue_s,他们都是dispatch_object_t类型。
typedef struct dispatch_object_s {
private:
dispatch_object_s();
~dispatch_object_s();
dispatch_object_s(const dispatch_object_s &);
void operator=(const dispatch_object_s &);
} *dispatch_object_t;
并且采用*dispatch_object_t又包装了dispatch_object_s,层层传递。
这种设计模式类似声明一个基类,然后层层继承下去,最底层都是这个基类。不过使用基类会导致后面类越来越庞大,难以维护,联合体的形式避免了这个缺点。
1.2 使用_dispatch_queue_init构造函数,初始化dq,第三个参数为width,并且有以下宏定义:
#define DISPATCH_QUEUE_WIDTH_FULL 0x1000ull
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
#define DISPATCH_QUEUE_WIDTH_MAX (DISPATCH_QUEUE_WIDTH_FULL - 2)
| 队列类型 | width |
|---|---|
| 串行 | 1 |
| 并发 | DISPATCH_QUEUE_WIDTH_MAX |
width代表能同时执行任务的最大数,DISPATCH_QUEUE_WIDTH_POOL后续说明。
1.3 设置队列dq的属性目标队列targetq为tq
dq->do_targetq = tq;
由此可知,dq和tq不是同一个东西,dq是最终初始化的对象,tq是执行block代码的队列。tq是dq的属性之一。
至此,队列的属性被赋值,目标队列被设置,初始化完成。
欣喜之余,思考下:
tq是怎么分配的,每次都需要初始化嘛?
关于tq的创建,上面的分析并没有提到。我们先回到最初的起点看看线程是怎么创建的?
dispatch_queue_t queue1 = dispatch_queue_create("juejin", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("juejin", NULL);
分别打印两个队列:
| 队列类型 | width | target |
|---|---|---|
| 串行 | 0x1 | com.apple.root.default-qos |
| 并发 | 0xffe | com.apple.root.default-qos.overcommit |
关于width在_dispatch_queue_init构造函数已经说明:
自己创建的串行队列为0x1,自己创建的并发队列为0xffe,还有一个宏定义为DISPATCH_QUEUE_WIDTH_POOL(0xfff),
搜索下DISPATCH_QUEUE_WIDTH_POOL
static const struct dispatch_queue_global_s _dispatch_custom_workloop_root_queue = {
DISPATCH_GLOBAL_OBJECT_HEADER(queue_global),
.dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE,
.do_ctxt = NULL,
.dq_label = "com.apple.root.workloop-custom",
.dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL),
.dq_priority = DISPATCH_PRIORITY_FLAG_MANAGER |
DISPATCH_PRIORITY_SATURATED_OVERRIDE,
.dq_serialnum = DISPATCH_QUEUE_SERIAL_NUMBER_WLF,
.dgq_thread_pool_size = 1,
};
DISPATCH_QUEUE_WIDTH_POOL是全局队列使用的。
关于target,dq的target是tq,那tq究竟是怎么创建的?
回到上面标记的2 * (qos - 1) + overcommit的值,
tq = _dispatch_get_root_queue(
qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos,
overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq;
_dispatch_get_root_queue内部通过数组模版获取对应的tq。
&_dispatch_root_queues[2 * (qos - 1) + overcommit];
查看下_dispatch_root_queues[]的定义:
struct dispatch_queue_global_s _dispatch_root_queues[] = {
#define _DISPATCH_ROOT_QUEUE_IDX(n, flags) \
((flags & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) ? \
DISPATCH_ROOT_QUEUE_IDX_##n##_QOS_OVERCOMMIT : \
DISPATCH_ROOT_QUEUE_IDX_##n##_QOS)
#define _DISPATCH_ROOT_QUEUE_ENTRY(n, flags, ...) \
[_DISPATCH_ROOT_QUEUE_IDX(n, flags)] = { \
DISPATCH_GLOBAL_OBJECT_HEADER(queue_global), \
.dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE, \
.do_ctxt = _dispatch_root_queue_ctxt(_DISPATCH_ROOT_QUEUE_IDX(n, flags)), \
.dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL), \
.dq_priority = flags | ((flags & DISPATCH_PRIORITY_FLAG_FALLBACK) ? \
_dispatch_priority_make_fallback(DISPATCH_QOS_##n) : \
_dispatch_priority_make(DISPATCH_QOS_##n, 0)), \
__VA_ARGS__ \
}
_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, 0,
.dq_label = "com.apple.root.maintenance-qos",
.dq_serialnum = 4,
),
_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.maintenance-qos.overcommit",
.dq_serialnum = 5,
),
_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, 0,
.dq_label = "com.apple.root.background-qos",
.dq_serialnum = 6,
),
_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.background-qos.overcommit",
.dq_serialnum = 7,
),
_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, 0,
.dq_label = "com.apple.root.utility-qos",
.dq_serialnum = 8,
),
_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.utility-qos.overcommit",
.dq_serialnum = 9,
),
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,
.dq_label = "com.apple.root.default-qos",
.dq_serialnum = 10,
),
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,
DISPATCH_PRIORITY_FLAG_FALLBACK | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.default-qos.overcommit",
.dq_serialnum = 11,
),
_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, 0,
.dq_label = "com.apple.root.user-initiated-qos",
.dq_serialnum = 12,
),
_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.user-initiated-qos.overcommit",
.dq_serialnum = 13,
),
_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, 0,
.dq_label = "com.apple.root.user-interactive-qos",
.dq_serialnum = 14,
),
_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.user-interactive-qos.overcommit",
.dq_serialnum = 15,
),
};
其内部声明了多种模版,而串行和并发通过计算得到的下标值的7和8,数下来正好对应com.apple.root.default-qos和com.apple.root.default-qos.overcommit,和打印的值不谋而合。
自定义串行和并发的下标取值因为overcommit的不同而不同。
所以,tq是根据串行和并发的值通过下标去模版数组直接获取的,不需要每次都去创建,提高性能。
那么,模版数组又是什么时候创建的?
可以想像,模版数组一定是伴随着libdispatch初始化时初始化的。
① 初始化模版数组
for循环中的DISPATCH_ROOT_QUEUE_COUNT是个多层嵌套的宏定义:
#define DISPATCH_ROOT_QUEUE_COUNT (DISPATCH_QOS_NBUCKETS * 2)
#define DISPATCH_QOS_NBUCKETS (DISPATCH_QOS_MAX - DISPATCH_QOS_MIN + 1)
#define DISPATCH_QOS_MIN DISPATCH_QOS_MAINTENANCE
#define DISPATCH_QOS_MAX DISPATCH_QOS_USER_INTERACTIVE
一顿宏定义后,DISPATCH_ROOT_QUEUE_COUNT为12。而_dispatch_root_queues[]内的模版的dq_serialnum是4-15(缺少1,2,3),刚好存在12个模板。
② 初始化主队列和全局队列
搜索_dispatch_main_q和_dispatch_mgr_q,
_dispatch_main_q是主队列。_dispatch_mgr_q是dq_label为com.apple.root.libdispatch-manager的队列,它的do_targetq指向_dispatch_mgr_root_queue,也就是全局队列。三者的dq_serialnum分别为1,2,3(补其模板数组最小值4之前的空缺)。
主队列和全局队列在libdispatch初始化时也被初始化,这也是为什么能在全局直接使用它们的原因。
看了看dispatch_get_main_queue的实现,_dispatch_main_q一目了然。
队列创建的总结:
- 自定义队列传的参数决定了
dqai,dqai决定了队列是串行还是并发 - 自定义队列的
目标队列(tq)是使用模版数组快速创建的 - 全局队列,主队列,模版数组是伴随者
libdispatch初始化的
3.2 任务执行
探索的重点在于:
block都需要手动调用,而GCD的任务没有显示调用,为什么会被执行
——————————————————————————————————————
在任务内部打上断点查看堆栈信息,
堆栈内_dispatch_client_callout异常明显
static inline void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
return f(ctxt);
}
GCD把任务进行包装成dispatch_function_t类型的参数f,在合适的时候系统会调用_dispatch_client_callout执行任务,而不需要外界显示调用。
3.3 同步函数
探索的重点在于:
- 同步函数为什么会顺序执行任务
- 同步函数使用过程可能出现的问题,比如死锁是怎么产生的
——————————————————————————————————————
同步dispatch_sync是我们熟悉的。直接搜索,不停跳转
dispatch_sync -> _dispatch_sync_f -> _dispatch_sync_f_inline,
参数func需要注意下:任务被包装成dispatch_function_t类型,在合适的地方调用。
① 先判断dq_width是否等于1,若相等就是串行队列,走_dispatch_barrier_sync_f,方法名有很熟悉的栅栏函数,这也从侧面说明了栅栏函数和同步串行是很类似的。
顺便回忆下死锁发生时的堆栈信息,
串行队列探索
居然发现,_dispatch_sync_f_slow赫然在列,似乎是那个点了。
_dispatch_barrier_sync_f_inline方法内部先通过_dispatch_tid_self获取线程ID,然后_dispatch_queue_try_acquire_barrier_sync判断线程是否在等待。如果等待进入_dispatch_sync_f_slow,否则调用_dispatch_lane_barrier_sync_invoke_and_complete最终也来到_dispatch_client_callout执行任务。
此时来到_dispatch_sync_f_slow,
方法内部先调用_dispatch_trace_item_push将任务用push的形式入队,队列因为是FIFO,所以实现了顺序的结构。
然后来到__DISPATCH_WAIT_FOR_QUEUE__,
通过调用_dispatch_wait_prepare从os底层获取队列任务状态,然后把刚才获取的任务的状态和此任务状态传入,(一个是执行同步函数自身的任务,一个是执行同步函数内block的任务)
_dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid)
{
return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}
两者进行异或运算,如果相同,即都为等待,则系统抛出死锁。
若不相同,则只是单方面等待,调用_dispatch_sync_invoke_and_complete_recurse递归执行任务。需要递归的原因是,任务的调度是系统底层行为,不知道等待何时结束。
GCD底层有大量调用os底层来获取状态回调的,是因为任务是否执行是依赖线程的状态是否良好,而线程的调度是系统底层行为
并发队列探索
② dq_width不等于1的情况,也就是并发队列,直接调用_dispatch_sync_invoke_and_complete执行任务。
内部调用_dispatch_sync_function_invoke_inline,然后来一个任务_dispatch_thread_frame_push一下,_dispatch_client_callout执行,执行完_dispatch_thread_frame_pop一下。同样实现了顺序执行的结构。
总结:
- 同步函数的任务采用了
push入队,pop出队的方式达到了顺序执行 - 同步函数当产生互相等待时,会发生
死锁 - 同步函数和栅栏函数是类似的
3.4 异步函数
探索的重点在于:
- 异步函数是怎么创建线程的
- 异步函数的任务是何时被包装的
——————————————————————————————————————
还是一样的思路,直接搜索dispatch_async(dis,
void
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
//获取当前线程中绑定的 queue.
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME;
dispatch_qos_t qos;
//包装任务并返回对应的优先级
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_f,
函数内部把任务及其相关属性包装起来。
- 在看
_dispatch_continuation_async,
dx_push是个宏定义调用dq_push,
#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
dq_push有很多赋值,
直接探索_dispatch_root_queue_push,因为其他的最终也会来到_dispatch_root_queue_push,只关注想要探索的点,不去关心太多的旁跟末节。
① 和 ② 的参数区别在于qos,看到这里,我们可以用反推法:
已知异步串行是顺序执行任务,异步并发是乱序执行任务。qos会影响任务调度的优先级。
所以可得:①是并发的分支,②是串行的分支。
虽然这里有分支,但是看下并发的分支,
简单的处理之后,就回到了串行分支的_dispatch_root_queue_push_inline,所以无论串行还是并发,最终都是来到_dispatch_root_queue_push_inline。并发的处理步骤多于串行也是可以理解的。
跳转:_dispatch_root_queue_push_inline -> _dispatch_root_queue_poke -> _dispatch_root_queue_poke_slow
can_request:能请求的线程数
t_count:线程池大小
remaining:剩下的
方法内部使用了两层do-whild循坏。两层循环动态的维护线程池的容量。
第一层是用来判断和控制线程池状态的,每次请求到线程remaining = can_request,线程池满了就不做处理;
第二层使用pthread_create来创建线程,请求到了--remaining,使剩下的线程总数减1。
总结:
- 异步函数使用
pthread_create创建线程 - 异步函数的任务在线程创建之前就被包装,等待被线程调度
3.5 单例
跳转:dispatch_once -> dispatch_once_f
① 传入的参数onceToken,被转换成dispatch_once_gate_t类型的变量l,l是底层原子操作的参数,这里通过os_atomic_load获取状态值v。
② 如果状态值为DLOCK_ONCE_DONE,说明任务已经执行过了,直接返回。
③ 如果此时任务没有执行过,则会在底层通过原子操作,将任务进行加锁,目的是为了保证当前任务执行的唯一性。加锁之后进行回调函数的执行,执行完成后,将当前任务解锁,将当前的任务状态置为DLOCK_ONCE_DONE。
④ 如果在任务执行期间,其他任务进来会调用_dispatch_once_wait进入无限次等待,原因是当前任务已经获取了锁,其他任务是无法获取锁的。
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
dispatch_function_t func){
_dispatch_client_callout(ctxt, func);
_dispatch_once_gate_broadcast(l);
}
_dispatch_once_callout内部调用_dispatch_client_callout执行任务,然后调用_dispatch_once_gate_broadcast进行广播修改任务状态值。
3.6 信号量
信号量三部曲:dispatch_semaphore_create,dispatch_semaphore_wait,dispatch_semaphore_signal
dispatch_semaphore_t
dispatch_semaphore_create(long value)
{
dispatch_semaphore_t dsema;
if (value < 0) {
return DISPATCH_BAD_INPUT;
}
_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_t类型且值为value的信号量。需要注意的是,信号量的起始值传递小于0的值将导致返回NULL。
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout){
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使传入的信号量dsema的值减1。os_atomic_dec2o底层被定义的符号为"-"
_os_atomic_c11_op((p), (v), m, sub, -)
如果value的值大于等于0(说明信号量大于等于1),该函数所处线程就继续执行下面的语句;否则,这个函数就阻塞当前线程。
如果等待的期间信号量的值被dispatch_semaphore_signal函数加1了,且是该函数所处线程获得了信号量,就继续向下执行并将信号量减1。
如果一直没有获取信号量,那么等到超时时间到来时,其所处线程也会自动执行后续语句。
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema){
long value = os_atomic_inc2o(dsema, dsema_value, release);
if (likely(value > 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_inc2o使信号量加1。os_atomic_inc2o底层被定义的符号为"+"。
_os_atomic_c11_op_orig((p), (v), m, add, +)
当value小于等于0时(说明信号量为负)表示当前并没有线程需要处理其后续语句。
当value大于0时,表示有线程(线程数等于value值)需要处理其后续语句,并且该函数唤醒了一个等待的线程,如果线程存在优先级,唤醒优先级最高的线程,否则随机唤醒。
3.7 调度组
dispatch_group_create:
dispatch_group_t
dispatch_group_create(void){
return _dispatch_group_create_with_count(0);
}
static inline dispatch_group_t
_dispatch_group_create_with_count(uint32_t n){
dispatch_group_t dg = _dispatch_object_alloc(DISPATCH_VTABLE(group),
sizeof(struct dispatch_group_s));
dg->do_next = DISPATCH_OBJECT_LISTLESS;
dg->do_targetq = _dispatch_get_default_queue(false);
if (n) {
os_atomic_store2o(dg, dg_bits,
-n * DISPATCH_GROUP_VALUE_INTERVAL, relaxed);
os_atomic_store2o(dg, do_ref_cnt, 1, relaxed);
}
return dg;
}
创建调度组,正常都是0,os底层不保存value;如果参数非0,os底层保存value为1。
dispatch_group_enter:
void
dispatch_group_enter(dispatch_group_t dg){
uint32_t old_bits = os_atomic_sub_orig2o(dg, dg_bits,
DISPATCH_GROUP_VALUE_INTERVAL, acquire);
uint32_t old_value = old_bits & DISPATCH_GROUP_VALUE_MASK;
...
}
调用os_atomic_sub_orig2o使任务计数value减1
dispatch_group_leave:
① 调用os_atomic_add_orig2o任务计数加1
② _dispatch_group_wake内部调用_dispatch_continuation_async(异步函数分析过)异步执行任务
③ leave次数如果多过enter,程序崩溃
dispatch_group_async:
dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq,
dispatch_block_t db)
{
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_GROUP_ASYNC;
dispatch_qos_t qos;
qos = _dispatch_continuation_init(dc, dq, db, 0, dc_flags);
_dispatch_continuation_group_async(dg, dq, dc, qos);
}
和异步函数但执行过程相似,_dispatch_continuation_init保存任务,_dispatch_continuation_group_async异步执行任务
需要注意但是,
异步执行任务对于group来说也是个任务,也是需要enter的,在执行任务后,也会调用一次leave。
4.写在后面
以上是对GCD底层很浅显的分析,它已经有些晦涩了,而它之下还有os底层,posix下层等..想想就头大。
不过探索源码就是个令人头秃的过程,换句话说也就是变强的过程。
--无论你从什么时候开始,重要的是开始后就不要停止。
后面陆续还会更新block,锁,内存管理等底层原理,敬请关注。