iOS多线程GCD(二) 源码分析同步函数、异步函数、单例

2,163 阅读6分钟

同步函数

同步函数是在当前线程执行,不会开辟线程,所以就先从同步dispatch_sync开始入手,然后再查看里面队列的区分

dispatch_sync

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);
}
// -   `work`就是我们需要研究的`block`,排除`unlikely`,然后就把目标放在
// `_dispatch_sync_f`方法,先来看看参数`_dispatch_Block_invoke`:
#ifdef __BLOCKS__
#define _dispatch_Block_invoke(bb) \
	 ((dispatch_function_t)((struct Block_layout *)bb)->invoke)
// -   这里的`bb`是传入的`work`,也就是`block`,
// 然后`_dispatch_Block_invoke(bb)`就是让`block`调用`invoke`,也就是`block`调用

_dispatch_sync_f

static void
_dispatch_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func,
		uintptr_t dc_flags)
{
     _dispatch_sync_f_inline(dq, ctxt, func, dc_flags);
}
// -   这里`ctxt`就是要研究的`block`,`func`是方法的调用`_dispatch_Block_invoke`

_dispatch_sync_f_inline

static inline void
_dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,
		dispatch_function_t func, uintptr_t dc_flags)
{
// 当`dq_width`值为`1`时是`串行队列`,
// 也就是这里串行会走`_dispatch_barrier_sync_f`方法
     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)));
}

同步函数串行队列

_dispatch_barrier_sync_f

image.png

image.png

_dispatch_lane_barrier_sync_invoke_and_complete

正常的同步函数串行队列

image.png

  • func是参数,但后面也有个DISPATCH_TRACE_ARG(void *dc)参数,但参数和参数之间怎么没有,隔开
  • DISPATCH_TRACE_ARG: #define DISPATCH_TRACE_ARG(arg) #define DISPATCH_TRACE_ARG(arg) , arg有参数时,它带上了, 再加参数,没有参数时代表空
_dispatch_sync_function_invoke_inline

image.png

_dispatch_client_callout

image.png

  • 这里不管likely(!u)条件是否限制,都会执行f(ctxt),也就是block调用
_dispatch_sync_f_slow

在上面_dispatch_barrier_sync_f_inline方法后,如果是死锁的情况,就会进入_dispatch_sync_f_slow方法

image.png

__DISPATCH_WAIT_FOR_QUEUE__

image.png

_dq_state_drain_locked_by
static inline bool
_dq_state_drain_locked_by(uint64_t dq_state, dispatch_tid tid)
{
    return _dispatch_lock_is_locked_by((dispatch_lock)dq_state, tid);
}


static inline bool
_dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid)
{
     // equivalent to _dispatch_lock_owner(lock_value) == tid
     return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}

#define DLOCK_OWNER_MASK			((dispatch_lock)0xfffffffc)

  • DLOCK_OWNER_MASK是个很大的值,也就是只有lock_valuetid相同时,与上DLOCK_OWNER_MASK的结果才会为0,也即是当前队列要等待的和需要执行的队列要等待的一样时,会出现你要等我,我要等你,进而产生死锁

同步函数并行队列

在上面_dispatch_sync_f_inline函数,如果dq_width不为1,就是并发队列,通过符号断点确认走_dispatch_sync_f_slow 方法里有两处用到了func,分别是_dispatch_sync_function_invoke_dispatch_sync_invoke_and_complete_recurse,再下符号断点确认,最终进入_dispatch_sync_function_invoke方法

_dispatch_sync_function_invoke

image.png _dispatch_sync_function_invoke_inline方法,和串行队列一样

_dispatch_sync_function_invoke_inline

image.png

_dispatch_client_callout

image.png

  • 最后通过f(ctxt)进行调用

流程图总结

image.png

异步函数

dispatch_async

image.png

_dispatch_continuation_alloc

static inline dispatch_continuation_t
_dispatch_continuation_alloc(void)
{
     dispatch_continuation_t dc =
	       _dispatch_continuation_alloc_cacheonly(); // 先从线程池获取线程可执行任务的线程
     if (unlikely(!dc)) { // 如果没有,就从堆中开辟一个空间创建一条
	 return _dispatch_continuation_alloc_from_heap(); 
     }
     return dc;
}
_dispatch_continuation_alloc_cacheonly
static inline dispatch_continuation_t
_dispatch_continuation_alloc_cacheonly(void)
{
     dispatch_continuation_t dc = (dispatch_continuation_t)
	       _dispatch_thread_getspecific(dispatch_cache_key);
     if (likely(dc)) {
	 _dispatch_thread_setspecific(dispatch_cache_key, dc->do_next);
     }
     return dc;
}
  • 根据key获取可执行的线程,如果获取到就调用dc->do_next给他一个可执行下个任务的状态,set和get方法如下:

image.png

  • 没有找到线程就执行_dispatch_continuation_alloc_from_heap方法
_dispatch_continuation_alloc_from_heap

image.png

_dispatch_continuation_async

  • 系统调用_dispatch_continuation_init函数,将blockdq(队列)dc(线程)生成一个dispatch_qos_t类型的qos,然后调用_dispatch_continuation_async
static inline void
_dispatch_continuation_async(dispatch_queue_class_t dqu,
		dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags)
{
//`block`调用,也就是`dqu`相关的
#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); 
     // #define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
}

block调用,也就是dqu相关的,所以就看dq_push

异步主队列_dispatch_main_queue_push

异步主队列的dp_push类型是_dispatch_main_queue_push

void
_dispatch_main_queue_push(dispatch_queue_main_t dq, dispatch_object_t dou,
		dispatch_qos_t qos)
{
     // Same as _dispatch_lane_push() but without the refcounting due to being
     // a global object
     if (_dispatch_queue_push_item(dq, dou)) {
	 return dx_wakeup(dq, qos, DISPATCH_WAKEUP_MAKE_DIRTY);
     }

     qos = _dispatch_queue_push_qos(dq, qos);
     if (_dispatch_queue_need_override(dq, qos)) {
	 return dx_wakeup(dq, qos, 0);
         // #define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)
     }
}

dq_wakeup,选择主队列的赋值_dispatch_main_queue_wakeup,它的实现如下:

_dispatch_main_queue_wakeup
void
_dispatch_main_queue_wakeup(dispatch_queue_main_t dq, dispatch_qos_t qos,
		dispatch_wakeup_flags_t flags)
{
#if DISPATCH_COCOA_COMPAT
     if (_dispatch_queue_is_thread_bound(dq)) {
	 return _dispatch_runloop_queue_wakeup(dq->_as_dl, qos, flags);
     }
#endif
     return _dispatch_lane_wakeup(dq, qos, flags);
}
_dispatch_runloop_queue_wakeup
void
_dispatch_runloop_queue_wakeup(dispatch_lane_t dq, dispatch_qos_t qos,
	  dispatch_wakeup_flags_t flags)
{
     if (unlikely(_dispatch_queue_atomic_flags(dq) & DQF_RELEASED)) {
	 // <rdar://problem/14026816>
	 return _dispatch_lane_wakeup(dq, qos, flags);
     }

     if (flags & DISPATCH_WAKEUP_MAKE_DIRTY) {
	 os_atomic_or2o(dq, dq_state, DISPATCH_QUEUE_DIRTY, release);
     }
     if (_dispatch_queue_class_probe(dq)) {
	 return _dispatch_runloop_queue_poke(dq, qos, flags);
     }

     qos = _dispatch_runloop_queue_reset_max_qos(dq);
     if (qos) {
	 mach_port_t owner = DISPATCH_QUEUE_DRAIN_OWNER(dq);
	 if (_dispatch_queue_class_probe(dq)) {
	     _dispatch_runloop_queue_poke(dq, qos, flags);
	 }
	 _dispatch_thread_override_end(owner, dq);
	 return;
     }
     if (flags & DISPATCH_WAKEUP_CONSUME_2) {
	 return _dispatch_release_2_tailcall(dq);
     }
}
_dispatch_runloop_queue_poke
static void
_dispatch_runloop_queue_poke(dispatch_lane_t dq, dispatch_qos_t qos,
		dispatch_wakeup_flags_t flags)
{
     // it's not useful to handle WAKEUP_MAKE_DIRTY because mach_msg() will have
     // a release barrier and that when runloop queues stop being thread-bound
     // they have a non optional wake-up to start being a "normal" queue
     // either in _dispatch_runloop_queue_xref_dispose,
     // or in _dispatch_queue_cleanup2() for the main thread.
     uint64_t old_state, new_state;

     if (dx_type(dq) == DISPATCH_QUEUE_MAIN_TYPE) {
	 dispatch_once_f(&_dispatch_main_q_handle_pred, dq,
				_dispatch_runloop_queue_handle_init);
     }

     ...
}
  • 这里就能看到主线程的判断,dispatch_once_f是一个单例方法,而第三个参数_dispatch_runloop_queue_handle_init就是要回调的方法,实现如下:

image.png

_dispatch_runloop_root_queue_create_4CF

image.png

_dispatch_main_queue_callback_4CF

image.png

  • 然后在单例方法dispatch_once_f中找到_dispatch_once_callout进行回调
_dispatch_once_callout

image.png

  • 最后走到了_dispatch_client_callout函数,进而进行block回调

异步串行队列_dispatch_lane_push

image.png

  • 这里会走到dx_wakeup->dq_wakeup中的_dispatch_lane_wakeup类型
_dispatch_lane_wakeup

截屏2021-08-16 16.20.48.png\

  • 然后会进入_dispatch_queue_wakeup方法
_dispatch_queue_wakeup

截屏2021-08-16 16.40.59.png\

  • 这里第一个参数已经改变,然后会进入_dispatch_queue_push_queue方法
_dispatch_queue_push_queue

截屏2021-08-16 16.43.27.png\

  • 根据反推法得知会进入_dispatch_event_loop_poke方法
_dispatch_event_loop_poke

截屏2021-08-16 17.26.43.png\

  • 这里会进入_dispatch_trace_item_push方法
_dispatch_trace_item_push

截屏2021-08-16 17.28.51.png\

  • 根据反推法会进入_dispatch_trace_continuation方法
_dispatch_trace_continuation

截屏2021-08-16 17.30.26.png\

  • 这是个宏定义,我们只需要找到相关func就可以,也就是_dispatch_lane_invoke
_dispatch_lane_invoke

截屏2021-08-16 17.32.07.png\

  • 由于_dispatch_queue_class_invoke是根据第五个参数invoke来获取tq,所以我们只需研究_dispatch_lane_invoke2
_dispatch_lane_invoke2

截屏2021-08-16 17.35.02.png\

  • 根据前面我们得知dq_width = 1时是串行,所以我们将目标放在_dispatch_lane_serial_drain函数
_dispatch_lane_serial_drain

截屏2021-08-16 17.37.17.png\

  • 在查看里面方法_dispatch_lane_drain
_dispatch_lane_drain

截屏2021-08-16 17.39.58.png\

  • 经过分析会走到_dispatch_continuation_pop_inline方法
_dispatch_continuation_pop_inline

截屏2021-08-16 17.41.15.png\

  • 在该函数里最终会走进_dispatch_continuation_invoke_inline方法
_dispatch_continuation_invoke_inline

截屏2021-08-16 17.41.27.png\

  • 最终在里面找到了_dispatch_client_callout函数

异步全局队列_dispatch_root_queue_push

源码如下: 截屏2021-08-16 10.27.54.png\

  • 根据分析,代码会走_dispatch_root_queue_push_inline方法
_dispatch_root_queue_push_inline
static inline void
_dispatch_root_queue_push_inline(dispatch_queue_global_t dq,
		dispatch_object_t _head, dispatch_object_t _tail, int n)
{
     struct dispatch_object_s *hd = _head._do, *tl = _tail._do;
     if (unlikely(os_mpsc_push_list(os_mpsc(dq, dq_items), hd, tl, do_next))) {
	 return _dispatch_root_queue_poke(dq, n, 0);
     }
}
复制代码
  • 然后会走_dispatch_root_queue_poke方法
_dispatch_root_queue_poke

截屏2021-08-16 10.31.23.png\

  • 然后来到_dispatch_root_queue_poke_slow方法:
_dispatch_root_queue_poke_slow
  • 这里面的主要调用相关方法比较隐蔽,在_dispatch_root_queues_init里面,它的实现如下:
_dispatch_root_queues_init
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_root_queues_init_once

截屏2021-08-16 14.10.48.png\

  • 这里主要是创建一个pthread实例,然后将任务_dispatch_worker_thread2交给它处理
_dispatch_worker_thread2

截屏2021-08-16 14.12.51.png\

  • 这里主要是获取root_queue,然后再调用_dispatch_root_queue_drain方法
_dispatch_root_queue_drain

截屏2021-08-16 14.15.17.png\

  • 然后再进入_dispatch_continuation_pop_inline方法
_dispatch_continuation_pop_inline

截屏2021-08-16 14.15.25.png\

  • 根据反推法,得知这里走了dx_invoke,根据dx_invoke找到do_invoke

截屏2021-08-16 14.26.07.png\

_dispatch_async_redirect_invoke

截屏2021-08-16 14.31.04.png\

  • 然后会进入_dispatch_continuation_pop方法
_dispatch_continuation_pop

截屏2021-08-16 14.32.18.png\

  • 方法里又回到_dispatch_continuation_pop_inline函数,此时进入函数会执行_dispatch_continuation_invoke_inline方法
_dispatch_continuation_invoke_inline

截屏2021-08-16 14.33.44.png\

  • 于是就找到了_dispatch_client_callout函数,也就是最终的回调函数处
线程处理

截屏2021-08-16 17.50.27.png

  • 这里的do-while是处理线程的开辟,首先需要拿到remaining的值为1,然后和can_request做对比,如果remaining大于can_request,则把can_request的值给remaining防止出错,当remaining0时,说明线程池已经满了
  • dgq_thread_pool_size是线程池的大小,根据搜索发现初始值为1
最大并发数

截屏2021-08-16 17.55.36.png\

  • 搜索发现dgq_thread_pool_size的值最大为DISPATCH_WORKQ_MAX_PTHREAD_COUNT
#define DISPATCH_WORKQ_MAX_PTHREAD_COUNT 255
复制代码

结论:线程池中理论最大的并发数是255

但占用的内存是多少呢?

占用内存

根据Thread Costs中相关说明,辅助线程最小堆栈大小为16KB ,并且大小必须是4KB的倍数 截屏2021-08-16 18.03.02.png\

  • 如果在一定的内存内,如果创建的线程内存越大,则能开辟的线程越少

异步并发队列_dispatch_lane_concurrent_push

截屏2021-08-16 14.47.14.png\

  • 根据符号断点得知会执行_dispatch_continuation_redirect_push方法
_dispatch_continuation_redirect_push

截屏2021-08-16 14.49.40.png\

  • 这里又进入了dx_push方法,但此时dq已经变了,会走_dispatch_root_queue_push,之后的流程和异步全局队列一样.

单例

dispatch_once

void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
    dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
复制代码
  • 底层只有控制参数val和回调方法block,原理就是通过改变变量来达到核心方法只执行一次的效果。

dispatch_once_f

  • 再来看看dispatch_once_f函数:
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);
    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);
}
复制代码
  • 这里先将val强转成dispatch_once_gate_t类型的l

  • 然后再根据&l->dgo_once判断是否已经执行过一次,如果已经执行过了,就直接返回

  • 如果没用执行过就来到_dispatch_once_gate_tryenter:

    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_client_callout

  • 如果加锁成功了,则执行_dispatch_once_callout函数:

    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_client_callout是进行函数回调
    • _dispatch_once_gate_broadcast函数是进行标记:

    截屏2021-08-16 10.10.33.png\

    • 这里_dispatch_once_mark_done的实现如下:
    static inline uintptr_t
    _dispatch_once_mark_done(dispatch_once_gate_t dgo)
    {
         return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release); // 将状态标记成 DLOCK_ONCE_DONE
    }
    复制代码
    
    • _dispatch_once_mark_done方法主要是将状态标记成DLOCK_ONCE_DONE,等下次执行单例时方便判断

_dispatch_once_wait

  • 如果没用执行,然后也被锁锁住了,就会执行_dispatch_once_wait方法进行等待,等待开锁