美文网首页
iOS libdispatch浅析

iOS libdispatch浅析

作者: FengyunSky | 来源:发表于2020-04-23 00:37 被阅读0次

    前言

    本文分析源码为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)
    

    理解队列与线程间的关系

    image.png
    只有主队列是绑定主线程,其他队列都是从线程池获取分配,并且线程的调度无需用户管理;

    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等功能;

    深入浅出 GCD 之 dispatch_once

    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已初始化的全局队列中根据qosovercommit获取指定队列;

    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-threadwidth为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调用堆栈如下:

    image.png
    结论:对于异步加入主队列的任务,首先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//将任务加入工作队列处理
    

    工作队列调用线程堆栈如下:

    image.png
    对于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 后面的语句的机会更大。通过这种方式,编译器在编译过程中,会将可能性更大的代码紧跟着起面的代码,从而减少指令跳转带来的性能上的下降。

    __builtin_expect 说明

    GCC __builtin_expect的作用

    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

    std::memory_order

    lldb 调试

    使用断点匹配及镜像匹配查找函数符号,可以查看所有链接库的函数符号并打断点,跟进函数调用栈;

    breakpoint set -r -n _dispatch_ //端点匹配的libdispatch.dylib函数
    image lookup -r -n _dispatch_ libdispatch.dylib //指定查找libdispatch.dylib动态库的匹配函数
    

    并且lldb支持python脚本解析,可以导入python脚本进行高级调试;

    Refenrence

    Dispatch

    iOS多线程总结

    扒了扒libdispatch源码

    GCD之线程原理

    相关文章

      网友评论

          本文标题:iOS libdispatch浅析

          本文链接:https://www.haomeiwen.com/subject/llsnihtx.html