欢迎阅读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
,锁
,内存管理
等底层原理,敬请关注。