前言
本文分析源码为libdispatch-1173.40.5,主要分析常用的dispatch API
具体的实现原理;先讲解下常用的数据结构体便于后续分析使用:
dispatch_object_s
struct dispatch_object_s {
const struct dispatch_object_vtable_s *do_vtable,
int volatile do_ref_cnt, //引用计数
int volatile do_xref_cnt, //外部引用计数,两者都为0时才会释放对象内存
struct dispatch_object_s *volatile do_next; //下一个do
struct dispatch_queue_s *do_targetq; //目标队列
void *do_ctxt; //上下文
void *do_finalizer; //销毁时调用函数
unsigned int do_suspend_cnt; //suspend暂停计数
};
其中do_vtable
包含了对象的类型及函数指针;
dispatch_object_t
dispatch_object_t
是个union的联合体,可以用dispatch_object_t
代表这个联合体里的所有数据结构。
typedef union {
struct _os_object_s *_os_obj;
struct dispatch_object_s *_do; //object结构体
struct dispatch_continuation_s *_dc; //任务,dispatch_aync的block会封装成这个数据结构
struct dispatch_queue_s *_dq; //队列
struct dispatch_queue_attr_s *_dqa; //队列属性
struct dispatch_group_s *_dg; //群组操作
struct dispatch_source_s *_ds; //source结构体
struct dispatch_mach_s *_dm;
struct dispatch_mach_msg_s *_dmsg;
struct dispatch_timer_aggregate_s *_dta;
struct dispatch_source_attr_s *_dsa; //source属性
struct dispatch_semaphore_s *_dsema; //信号量
struct dispatch_data_s *_ddata;
struct dispatch_io_s *_dchannel;
struct dispatch_operation_s *_doperation;
struct dispatch_disk_s *_ddisk;
} dispatch_object_t __attribute__((__transparent_union__));
dispatch_continuation_s
struct dispatch_continuation_s {
struct dispatch_object_s *volatile do_next; // 下一个任务
dispatch_function_t dc_func; // 执行的方法
void *dc_ctxt; // 方法上下文
void *dc_data; // 相关数据
void *dc_other // 其它信息
}
dispatch_continuation_s
是中的任务的结构体,被传入的 block
会被变成这个结构体对象塞入队列;
dispatch_queue_s
struct dispatch_queue_s {
struct dispatch_queue_s *do_targetq; // 目标队列,这个最终会指向一个系统的默认队列
struct dispatch_object_s *volatile dq_items_head; // 队列头部
struct dispatch_object_s *volatile dq_items_tail; // 队列尾部
unsigned long dq_serialnum; // 队列序号
const char *dq_label; // 队列名
dispatch_priority_t dq_priority; // 优先级
dispatch_priority_t volatile dq_override; // 是否被覆盖
uint16_t dq_width; // 可并发执行的任务数
dispatch_queue_t dq_specific_q; // 特殊队列
uint32_t dq_side_suspend_cnt; // 暂停的任务数
const struct queue_vtable_s *do_vtable { // 队列的一些函数指针
unsigned long const do_type; // 队列类型,例如:DISPATCH_QUEUE_CONCURRENT_TYPE、DISPATCH_QUEUE_SERIAL_TYPE、DISPATCH_QUEUE_GLOBAL_ROOT_TYPE ...
const char *const do_kind; // 队列种类,例如:"serial-queue"、"concurrent-queue"、"global-queue"、"main-queue"、"runloop-queue""mgr-queue" ...
void (*const do_dispose)(/*params*/); // 销毁队列
void (*const do_suspend)(/*params*/); // 暂停队列
void (*const do_resume)(/*params*/); // 恢复队列
void (*const do_invoke)(/*params*/); // 开始处理队列
void (*const do_wakeup)(/*params*/); // 唤醒队列
void (*const do_set_targetq)(/*params*/); // 设置target queue
};
}
dispatch_queue_s
是队列的结构体,在它的 do_vtable
中有很多函数指针,对应队列的一些操作方法,对应有一些宏可以调用队列中的这些方法。比如, do_dispose
方法对应有一个宏 dx_dispose
:
#define dx_dispose(queue) &(queue)->do_vtable->_os_obj_vtable->do_dispose(queue)
理解队列与线程间的关系
只有主队列是绑定主线程,其他队列都是从线程池获取分配,并且线程的调度无需用户管理;
dispatch_once
源码如下:
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
//原子性获取l->dgo_once的值
uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
//判定上面的值是否为DLOCK_ONCE_DONE(大概率是,表明已经被赋值执行func),是则直接返回
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);
}
//线程阻塞等待dispatch_function_t func执行完成
return _dispatch_once_wait(l);
}
static inline bool
_dispatch_once_gate_tryenter(dispatch_once_gate_t l)
{
//原子性的判断l->dgo_once是否等于DLOCK_ONCE_UNLOCKED(表示未赋值),
//若是则赋值为线程id
return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
(uintptr_t)_dispatch_lock_value_for_self(), relaxed);
}
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
dispatch_function_t func)
{
//调用block中的func
_dispatch_client_callout(ctxt, func);
//广播唤醒所有等待的线程
_dispatch_once_gate_broadcast(l);
}
大概流程如下(摘抄别人的图):
image.png
常用于单例及swizzeld method等功能;
queue
queue
队列主要使用的API如下:
dispatch_queue_main_t
dispatch_get_main_queue(void);
dispatch_queue_global_t
dispatch_get_global_queue(long identifier, unsigned long flags);
dispatch_queue_t
dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
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核心函数伪代码如下:
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq, bool legacy)
{
//Step 1: Normalize arguments (qos, overcommit, tq)
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);//对于串行队列(dqa为NULL)返回空属性对象,对于并行队列会默认初始化值
//初始化qos优先级,overcommit是否需要创建新线程,tq目标队列默认为DISPATCH_TARGET_QUEUE_DEFAULT
if(!tq) {
tq = _dispatch_root_queues[2 * (qos - 1) + overcommit];//如未指定目标队列则从root队列数组中获取
}
//Step 2: Initialize the queue 核心代码如下:
//申请内存空间
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s));
//初始化序列号从17开启,其余如下:
// skip zero
// 1 - main_q //主队列
// 2 - mgr_q //管理队列
// 3 - mgr_root_q//管理队列的目标队列
// 4,5,6,7,8,9,10,11,12,13,14,15 - global queues,全局队列已在root队列数组中初始化,分别根据qos优先级指定不同队列
// 17 - workloop_fallback_q
// we use 'xadd' on Intel, so the initial value == next assigned
dq->dq_serialnum = os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);
//队列并发数,若串行队列则为1,并行队列为DISPATCH_QUEUE_WIDTH_MAX
dq->dq_width = dqai.dqai_concurrent ? DISPATCH_QUEUE_WIDTH_MAX : 1;
dq->dq_label = label;//指定名称
dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
dqai.dqai_relpri);//指定优先级
dq->do_targetq = tq;//指定目标队列
return dq._dq;
}
//已分配的全局队列,指定了不同qos class的队列
struct dispatch_queue_global_s _dispatch_root_queues[] = {
//初始化属性
...
_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,
),
};
dispatch_queue_create
主要是创建队列并初始化其参数,包括名称、优先级、并发数、序列号及目标队列,由上面的"线程与队列关系图"可以看出,指定的目标队列都是指向线程池;
dispatch_get_global_queue
//核心函数
static inline dispatch_queue_global_t
_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
{
if (unlikely(qos < DISPATCH_QOS_MIN || qos > DISPATCH_QOS_MAX)) {
DISPATCH_CLIENT_CRASH(qos, "Corrupted priority");
}
return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
}
主要是从_dispatch_root_queues
已初始化的全局队列中根据qos
及overcommit
获取指定队列;
dispatch_get_main_queue
//核心代码
struct dispatch_queue_static_s _dispatch_main_q = {
DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
#if !DISPATCH_USE_RESOLVERS
.do_targetq = _dispatch_get_default_queue(true),
#endif
.dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
DISPATCH_QUEUE_ROLE_BASE_ANON,
.dq_label = "com.apple.main-thread",
.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
.dq_serialnum = 1,
};
主队列对应的就是_dispatch_main_q
队列,其队列名为com.apple.main-thread
,width
为1表示串行队列,序列号为1;
async
void dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
//分配dispatch_continuation内存
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME;
dispatch_qos_t qos;
//初始化,主要copy(work)避免block执行前被销毁;
//指定dc->dc_ctxt=work;
//dc->func=_dispatch_call_block_and_release;用于block执行完成后释放
qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
//_dispatch_continuation_async核心函数为dx_push即do_vtable中保存的函数指针
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
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);//核心就是do_vtable中保存的dq_push函数指针
}
dx_push
函数指针是在init.c
初始化时指定,具体如下:
//workloop class instance vtable
_dispatch_workloop_push
//queue_serial,queue_runloop,source,channel,mach 串行/runloop/source/channel/mach
_dispatch_lane_push
//queue_concurrent并发队列
_dispatch_lane_concurrent_push
//queue_global,queue_pthread_root 全局队列及root线程
_dispatch_root_queue_push
//queue_mgr管理队列
_dispatch_mgr_queue_push
//queue_main主队列
_dispatch_main_queue_push
下面分析常见的主队列及全局队列:
void _dispatch_main_queue_push(dispatch_queue_main_t dq, dispatch_object_t dou,
dispatch_qos_t qos)
{
//伪代码
_disaptch_queue_push();//push到主队列
dx_wakeup()//调用do_vtable中的dq_wakeup函数唤醒队列并执行
}
mac平台(ios存在些差异)调用栈:
dx_wakeup
_dispatch_main_queue_wakeup
_dispatch_runloop_queue_wakeup
_dispatch_runloop_queue_poke
_dispatch_runloop_queue_handle_init//dispatch_once_f只执行一次用于初始化,仅支持mac,不支持iphone平台
mach_port_construct//构建线程mach port
_dispatch_runloop_queue_set_handle
dq->do_ctxt = (void *)(uintptr_t)handle
_dispatch_send_wakeup_runloop_thread//通过上面投建的mach port唤醒runloop
主线程runloop
调用堆栈如下:
结论:对于异步加入主队列的任务,首先
push
压入主队列,并通过mach port
调用_dispatch_send_wakeup_runloop_thread
发送mach port
消息至主线程runloop
,以唤醒主线程runloop
调用libdispatch.dylib
动态库中的_dispatch_main_queue_callback_4CF
函数,进而触发block
调用;
global queue
调用栈:
_dispatch_root_queue_push
_dispatch_root_queue_push_override
_dispatch_root_queue_push_inline//os_mpsc_push_list原子性push到队列中
_dispatch_root_queue_poke
_dispatch_root_queue_poke_slow
_pthread_workqueue_addthreads//将任务加入工作队列处理
工作队列调用线程堆栈如下:
对于
global quque
全局队列异步并发执行任务,就是将任务push
压入root queue
,并触发工作队列线程执行pop
任务出队列并执行;
其他队列类型可通过源码及调用栈跟踪分析,只要明白工作原理即可,源码需要处理各种情况的队列逻辑比较复杂;
Sync
dispatch_sync
加入全局队列分析:
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"global queue sync fired");
});
dispatch_sync
调用栈如下:
dispatch_sync
_dispatch_sync_f
_dispatch_sync_f_inline
_dispatch_barrier_sync_f//串行队列
_dispatch_queue_try_acquire_barrier_sync//尝试加锁处理,若加锁失败,则表明当前队列正在被调度
_dispatch_sync_f_slow
_dispatch_sync_recurse//存在多个目标队列的嵌套调用则递归调用
_dispatch_lane_barrier_sync_invoke_and_complete//不存在上述情况,队列立即触发调用
_dispatch_sync_f_slow//并发队列
_dispatch_sync_function_invoke
_dispatch_client_callout
__main_block_invoke
同步执行任务,若存在任务正在执行或者当前队列被调度,则通过加锁(信号量或者kevent形式)等待任务执行后再继续执行,否则直接执行,以保证同步执行;
小知识
__builtin_expect
gcc
引入的用于优化编译器的指令,避免指令跳转,提升cpu的效率;
将流水线引入cpu,可以提高cpu的效率。更简单的说,让cpu可以预先取出下一条指令,可以提供cpu的效率。如下图所示:
可见,cpu流水钱可以减少cpu等待取指令的耗时,从而提高cpu的效率。 如果存在跳转指令,那么预先取出的指令就无用了。cpu在执行当前指令时,从内存中取出了当前指令的下一条指令。执行完当前指令后,cpu发现不是要执行下一条指令,而是执行offset偏移处的指令。cpu只能重新从内存中取出offset偏移处的指令。因此,跳转指令会降低流水线的效率,也就是降低cpu的效率。 综上,在写程序时应该尽量避免跳转语句。那么如何避免跳转语句呢?答案就是使用builtin_expect。 这个指令是gcc引入的,作用是"允许程序员将最有可能执行的分支告诉编译器"。这个指令的写法为:builtin_expect(EXP, N)。意思是:EXP==N的概率很大。一般的使用方法是将__builtin_expect指令封装为likely和unlikely宏,常用于内核编程,这两个宏的写法如下:#define likely(x) __builtin_expect(!!(x), 1) //x很可能为真 #define unlikely(x) __builtin_expect(!!(x), 0) //x很可能为假
不过要明确:
if(likely(value)) //等价于 if(value) if(unlikely(value)) //也等价于 if(value)
但是,使用
likely()
,执行 if 后面的语句的机会更大,使用unlikely()
,执行 else 后面的语句的机会更大。通过这种方式,编译器在编译过程中,会将可能性更大的代码紧跟着起面的代码,从而减少指令跳转带来的性能上的下降。
os_atomic_cmpxchg
直接上结论:可以理解为p
变量相当于atomic_t
类型的ptr
指针用于获取当前内存访问制约规则m
的值,用于对比旧值e
,若相当就赋值新值v
;
#define os_atomic_cmpxchg(p, e, v, m) \
({ _os_atomic_basetypeof(p) _r = (e); \
atomic_compare_exchange_strong_explicit(_os_atomic_c11_atomic(p), \
&_r, v, memory_order_##m, memory_order_relaxed); })
typedef enum memory_order {
memory_order_relaxed = __ATOMIC_RELAXED,
memory_order_consume = __ATOMIC_CONSUME,
memory_order_acquire = __ATOMIC_ACQUIRE,
memory_order_release = __ATOMIC_RELEASE,
memory_order_acq_rel = __ATOMIC_ACQ_REL,
memory_order_seq_cst = __ATOMIC_SEQ_CST
} memory_order;
对比参考linux
原子操作函数atomic_cmpxchg
:
static inline int atomic_cmpxchg(atomic_t *ptr, int old, int new)
atomic_cmpxchg
用于对 atmoic 变量的值进行对比,如果与内存中的值 相等,那么就替换成新值。参数 ptr 指向一个 atomic_t 变量;参数 old 代表与内存中对比 的值;new 代表替换的值。
atomic_cmpxchg源码分析
Linux原子操作 atomic_cmpxchg()/Atomic_read()/Atomic_set()/Atomic_add()/Atomic_sub()/atomi
lldb 调试
使用断点匹配及镜像匹配查找函数符号,可以查看所有链接库的函数符号并打断点,跟进函数调用栈;
breakpoint set -r -n _dispatch_ //端点匹配的libdispatch.dylib函数
image lookup -r -n _dispatch_ libdispatch.dylib //指定查找libdispatch.dylib动态库的匹配函数
并且lldb
支持python
脚本解析,可以导入python
脚本进行高级调试;
网友评论