美文网首页
多线程(二)

多线程(二)

作者: 镜像 | 来源:发表于2022-02-27 18:28 被阅读0次

    我们都知道,程序启动就会创建一个主线程来执行程序,我们先看一下默认开启的主线程的相关信息。在main函数打一个断点,看函数调用栈:

    main

    可以看到当前线程:Queue: com.apple.main-thread(serial),它的名字是com.apple.main-thread,它的类型是serial也就是串行队列。
    这个主线程是什么时候创建又是怎么调用的呢?我们可以看到在main函数之前,主线程就已经被创建好,猜测应该是dyld链接之后,main函数之前创建的。那我们怎么证明呢?这就要去libdispatch源码中找线索了。

    在源码中找类似init操作看有没有主线程相关的初始化。全局搜索dispatch_get_main_queue(,找到下面实现:

    DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_CONST DISPATCH_NOTHROW
    dispatch_queue_main_t
    dispatch_get_main_queue(void)
    {
        return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
    }
    
    #define DISPATCH_GLOBAL_OBJECT(type, object) ((type)&(object))
    

    这个宏定义第一个参数type是类型,值是object。也就是说dispatch_queue_main_t是类型,_dispatch_main_q这个才是真正的值。全局进行搜索,发现结果有很多,这个变量要调用一定会有赋值的地方,尝试搜索_dispatch_main_q =,就会发现只有一个地方赋值:

    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,
    };
    

    从上面代码可以看到,主队列的名字com.apple.main-thread,跟我们上面看函数调用栈的结果一样,也就是没找错地方。主队列的类型DQF_WIDTH(1),串行队列,很多人说.dq_serialnum = 1看到这个num = 1就是主队列,其实不是,这只是队列的编号,怎么证明呢?串行队列必然相对并发队列有某些特性,我们找的就是这些特性来证明。

    我们创建队列,都是用dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)这个函数来创建,现在来研究下创建的底层实现。

    全局搜索dispatch_queue_create(con,为什么要加(con因为我们要找他的底层实现,第一个参数是const类型,这样搜索结果更少方便定位。

    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的实现,有100多行代码,我们研究的重点是看它创建的队列是什么,也就是返回的是什么队列,所以我们先关注返回值。

    返回值是_dispatch_trace_queue_create(dq)._dq,里面trace的含义就是方便追踪,不是关注重点,重点是里面的dq,我们看dq是如何创建的。从_dispatch_lane_create_with_target方法中找相关代码:

    dispatch_lane_t dq = _dispatch_object_alloc(vtable,
                sizeof(struct dispatch_lane_s));
    _dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
                DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
                (dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));
    

    第一行是开辟内存空间,第二行是构造函数。看构造函数第三个参数,dqai.dqai_concurrent ? DISPATCH_QUEUE_WIDTH_MAX : 1,命名意思比较明显,判断是不是并发队列,如果是传max,如果不是并发队列(即串行队列),参数就传1。

    我们再看构造函数的实现,看这个参数是如何使用的:

    _dispatch_queue_init

    可以看到参数使用dqf |= DQF_WIDTH(width);,也就是为什么我们上面说DQF_WIDTH(1)是队列的类型。
    再看下dq_serialnum,它这个值是根据os_atomic_inc_orig创建

    #define os_atomic_inc_orig(p, m) \
            os_atomic_add_orig((p), 1, m)
    
    #define os_atomic_add_orig(p, v, m) \
            _os_atomic_c11_op_orig((p), (v), m, add, +)
    
    #define _os_atomic_c11_op_orig(p, v, m, o, op) \
            atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), v, \
            memory_order_##m)
    

    这个函数最后就会变成atomic_fetch_add_explicit(_os_atomic_c11_atomic(p), 1, memory_order_relaxed),这个宏一层层封装最后就是C11标准的函数,atomic_fetch_add_explicit可以理解是把前两个参数想加并返回,_os_atomic_c11_atomic表示是原子操作。

    那看下第一个参数的值_dispatch_queue_serial_numbers是什么:

    // 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
    // 17 - workloop_fallback_q
    // we use 'xadd' on Intel, so the initial value == next assigned
    #define DISPATCH_QUEUE_SERIAL_NUMBER_INIT 17
    extern unsigned long volatile _dispatch_queue_serial_numbers;
    

    可以看到1是主队列的意思,4-15全局队列,我们用dispatch_get_global_queue创建的队列为什么没有2、3在注释中可以得到解释。

    这里有个想法,这个队列和线程的num是不是一致的呢?我们创建几个线程并打印:

    线程

    明显看到线程num有等于3(不一定一次运行就出现,我也是运行了好多次),也就是线程的num和队列的num并不是一个东西,不要混淆了。

    我们分别获取4种不同的线程打印下线程信息:

    线程

    去源码中搜索com.apple.root.default-qos,可以看到全局队列对应的num是10

    _DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,
        .dq_label = "com.apple.root.default-qos",
        .dq_serialnum = 10,
    ),
    

    我们现在再回到_dispatch_lane_create_with_target这个方法中,看第一行代码

    dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
    

    这行代码的意义就是对线程一些信息进行面向对象的封装。再全局搜索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实现,里面有个小细节,如果dqa是空的话,就直接返回。

    _dispatch_queue_attr_to_info

    dispatch_queue_create创建队列的时候,如果attr传空,通过dqai.dqai_concurrent判断就会是串行队列,如果attrDISPATCH_QUEUE_SERIAL,可以看到这个宏的实现#define DISPATCH_QUEUE_SERIAL NULL也是空,所以他们是等价的,都是创建串行队列。

    队列的类型

    无论我们直接获取主队列或者全局队列又或者自己创建的队列,最后我们接收的类型都是dispatch_queue_t,点进去看这个类型:DISPATCH_DECL(dispatch_queue);,这里我们可以看到有好几个宏定义,他们判断不同,第一个#if OS_OBJECT_USE_OBJC,正常会走到这个if里面。
    再继续点就可以看到

    #define DISPATCH_DECL(name) OS_OBJECT_DECL_SUBCLASS(name, dispatch_object)
    

    继续往下看发现没有了,我们只能从源码中搜索OS_OBJECT_DECL_SUBCLASS,找到三个宏定义:

    #define OS_OBJECT_DECL_SUBCLASS(name, super)  DISPATCH_DECL(name)
    

    这个相当于跟上面DISPATCH_DECL宏定义死循环了,不是这个。

    #define OS_OBJECT_DECL_SUBCLASS_SWIFT(name, super) \
            OS_EXPORT OS_OBJECT_OBJC_RUNTIME_VISIBLE \
            OS_OBJECT_DECL_IMPL_CLASS(name, OS_OBJECT_CLASS(super))
    

    这个的条件是#if OS_OBJECT_SWIFT3,也不是这个。
    就剩下一个:

    #define OS_OBJECT_DECL_SUBCLASS(name, super) \
            OS_OBJECT_DECL_IMPL(name, <OS_OBJECT_CLASS(super)>)
    

    继续往下探索这个宏定义实现:

    #define OS_OBJECT_DECL_IMPL(name, ...) \
            OS_OBJECT_DECL_PROTOCOL(name, __VA_ARGS__) \
            typedef NSObject<OS_OBJECT_CLASS(name)> \
                    * OS_OBJC_INDEPENDENT_CLASS name##_t
    
    #define OS_OBJECT_DECL_PROTOCOL(name, ...) \
            @protocol OS_OBJECT_CLASS(name) __VA_ARGS__ \
            @end
    
    #define OS_OBJECT_CLASS(name) OS_##name
    
    1. DISPATCH_DECL(dispatch_queue);
    2. OS_OBJECT_DECL_SUBCLASS(dispatch_queue, dispatch_object)
    3. OS_OBJECT_DECL_IMPL(dispatch_queue, <OS_OBJECT_CLASS(dispatch_object)>)
    4. OS_OBJECT_DECL_PROTOCOL(dispatch_queue, <OS_OBJECT_CLASS(dispatch_object)>)
      typedef NSObject<OS_OBJECT_CLASS(dispatch_queue)>
      * OS_OBJC_INDEPENDENT_CLASS dispatch_queue_t
    5. @protocol OS_OBJECT_CLASS(dispatch_queue) <OS_OBJECT_CLASS(dispatch_object)>
      @end
      typedef NSObject<OS_OBJECT_CLASS(dispatch_queue)>
      * OS_OBJC_INDEPENDENT_CLASS dispatch_queue_t
    6. @protocol os_dispatch_queue <os_dispatch_object>
      @end
      typedef NSObject<os_dispatch_queue>
      * OS_OBJC_INDEPENDENT_CLASS dispatch_queue_t

    也就是dispatch_queue_t本质是一个满足os_dispatch_queue协议的对象。

    第二个#elif defined(__cplusplus) && !defined(__DISPATCH_BUILDING_DISPATCH__),判断是C++第二个条件是YES,也就是说底层C++实现可能会走到这个if

    #define DISPATCH_DECL(name) \
            typedef struct name##_s : public dispatch_object_s {} *name##_t
    

    typedef struct dispatch_queue_s : public dispatch_object_s {} *dispatch_queue_t

    也就是这种情况dispatch_queue_s类型继承dispatch_object_s的结构体。
    在源码中我们还能看到一个联合体:

    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_channel_s *_dch;
        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这个可以是联合体里面的各种类型。

    之后的条件就没必要看了,基本不会走。

    它的底层结构因为底层是C++,所以我们看第二个,看下dispatch_queue_s结构体的实现:

    struct dispatch_queue_s {
        DISPATCH_QUEUE_CLASS_HEADER(queue, void *__dq_opaque1);
        /* 32bit hole on LP64 */
    } DISPATCH_ATOMIC64_ALIGN;
    
    #define DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \
        _DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__); \
        /* LP64 global queue cacheline boundary */ \
        unsigned long dq_serialnum; \
        const char *dq_label; \
        DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags, \
            const uint16_t dq_width, \
            const uint16_t __dq_opaque2 \
        ); \
        dispatch_priority_t dq_priority; \
        union { \
            struct dispatch_queue_specific_head_s *dq_specific_head; \
            struct dispatch_source_refs_s *ds_refs; \
            struct dispatch_timer_source_refs_s *ds_timer_refs; \
            struct dispatch_mach_recv_refs_s *dm_recv_refs; \
            struct dispatch_channel_callbacks_s const *dch_callbacks; \
        }; \
        int volatile dq_sref_cnt
    
    #define _DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \
        DISPATCH_OBJECT_HEADER(x); \
        __pointer_sized_field__; \
        DISPATCH_UNION_LE(uint64_t volatile dq_state, \
                dispatch_lock dq_state_lock, \
                uint32_t dq_state_bits \
        )
    #endif
    
    #define DISPATCH_OBJECT_HEADER(x) \
        struct dispatch_object_s _as_do[0]; \
        _DISPATCH_OBJECT_HEADER(x)
    
    #define _DISPATCH_OBJECT_HEADER(x) \
        struct _os_object_s _as_os_obj[0]; \
        OS_OBJECT_STRUCT_HEADER(dispatch_##x); \
        struct dispatch_##x##_s *volatile do_next; \
        struct dispatch_queue_s *do_targetq; \
        void *do_ctxt; \
        void *do_finalizer
    
    #define OS_OBJECT_STRUCT_HEADER(x) \
        _OS_OBJECT_HEADER(\
        const struct x##_vtable_s *do_vtable, \
        do_ref_cnt, \
        do_xref_cnt)
    
    #define _OS_OBJECT_HEADER(isa, ref_cnt, xref_cnt) \
            isa; /* must be pointer-sized */ \
            int volatile ref_cnt; \
            int volatile xref_cnt
    

    一层一层宏包装的继承链。

    GCD任务块执行时机

    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
    });
    

    上面代码在全局并发队列同步执行一个打印,那这个block块里面的代码是什么时候实现的呢?从源码中找线索:

    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
    搜索_dispatch_Block_invoke可以看到是个宏定义:

    #define _dispatch_Block_invoke(bb) \
            ((dispatch_function_t)((struct Block_layout *)bb)->invoke)
    

    执行invoke也就是这个就是work的调用,继续看_dispatch_sync_f是从哪调用过来的,

    DISPATCH_NOINLINE
    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就是workfunc就是block函数。

    _dispatch_sync_f_inline

    我们看到_dispatch_sync_f_inline函数有好几个return,到底走哪个也不好判断,这时候我们就把return对应函数加到符号断点里面,看到底走哪个。

    符号断点 image

    看到断点走到了_dispatch_sync_f_slow位置,在源码中搜索_dispatch_sync_f_slow的实现,里面也是有几个return,我们故技重施,继续添加符号断点并且重新运行,

    image

    看到后面走到的_dispatch_sync_function_invoke这个方法,

    static void
    _dispatch_sync_function_invoke(dispatch_queue_class_t dq, void *ctxt,
            dispatch_function_t func)
    {
        _dispatch_sync_function_invoke_inline(dq, ctxt, func);
    }
    
    static inline void
    _dispatch_sync_function_invoke_inline(dispatch_queue_class_t dq, void *ctxt,
            dispatch_function_t func)
    {
        dispatch_thread_frame_s dtf;
        _dispatch_thread_frame_push(&dtf, dq);
        _dispatch_client_callout(ctxt, func);
        _dispatch_perfmon_workitem_inc();
        _dispatch_thread_frame_pop(&dtf);
    }
    
    

    参数ctxt就是之前的workfunc就是之前block包装的函数,跟这个有关的就是_dispatch_client_callout(ctxt, func);,全局搜索_dispatch_client_callout(void,有几个地方:

    static inline void
    _dispatch_client_callout(void *ctxt, dispatch_function_t f)
    {
        return f(ctxt);
    }
    
    void
    _dispatch_client_callout(void *ctxt, dispatch_function_t f)
    {
        @try {
            return f(ctxt);
        }
        @catch (...) {
            objc_terminate();
        }
    }
    
    void
    _dispatch_client_callout(void *ctxt, dispatch_function_t f)
    {
        _dispatch_get_tsd_base();
        void *u = _dispatch_get_unwind_tsd();
        if (likely(!u)) return f(ctxt);
        _dispatch_set_unwind_tsd(NULL);
        f(ctxt);
        _dispatch_free_unwind_tsd();
        _dispatch_set_unwind_tsd(u);
    }
    

    这两个无论调用哪个,核心思想都是调用f(ctxt),也就是block中的函数。

    总结下dispatch_syncblock调用:

    1. dispatch_sync
    2. _dispatch_sync_f
    3. _dispatch_sync_f_inline
    4. _dispatch_sync_f_slow
    5. _dispatch_sync_function_invoke
    6. _dispatch_sync_function_invoke_inline
    7. _dispatch_client_callout
    8. f(ctxt)

    block中打个断点,看下函数调用栈:

    image

    跟我们分析一致。

    接下来我们看下dispatch_asyncblock的调用时机。

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
    });
    

    全局搜索dispatch_async(dis

    void
    dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
    {
        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);
    }
    

    我们还是关注work,找_dispatch_continuation_init的实现

    _dispatch_continuation_init

    unlikely不用关注,再找_dispatch_continuation_init_f实现

    _dispatch_continuation_init_f _dispatch_continuation_priority_set

    也就是说,qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);这行代码的意义就是把block进行对应的封装以及优先级处理。因为是异步的,所以需要优先级来进行函数执行的参考和依据。
    函数想要执行,肯定就要依赖下面的代码_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);

    _dispatch_continuation_async
    #define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
    

    因为函数最后包装在qos里面,所以我们关注z这个参数,全局搜索dq_push

    image

    全局并发队列是在这赋值,找_dispatch_root_queue_push实现:

    _dispatch_root_queue_push

    里面第三个参数是函数封装,并没有找到相关的调用,看最后方法,继续找:

    DISPATCH_ALWAYS_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_slow
    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

    我们先看下函数调用栈:


    image

    函数调用是从_dispatch_worker_thread2这个函数调用过来,在方法里面找这个函数相关内容:

    _pthread_workqueue_init_with_workloop
    这个就是底层pthread的相关封装,它是通过workloop来控制是否调用,这个workloop又是通过OS控制,受CPU的调度处理。

    死锁分析

    dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"1");
        dispatch_sync(queue, ^{
            NSLog(@"2");
        });
        NSLog(@"3");
    });
    

    我们看上面的一段代码,运行就会发现崩溃,可以看到崩溃的函数调用栈:

    死锁崩溃

    死锁崩溃我们可以看到先走到_dispatch_sync_f_slow函数,然后再走__DISPATCH_WAIT_FOR_QUEUE__发生崩溃,那现在从源码分析一下什么情况下回产生死锁。

    我们上面已经分析到,dispatch_sync的流程,一开始流程没有区别:

    1. dispatch_sync
    2. _dispatch_sync_f
    3. _dispatch_sync_f_inline

    走到这都一样,但是接下来,就不太一样了,看likely(dq->dq_width == 1)这个条件,上面分析过dq_width为1时为串行队列,死锁就是在串行队列才会有,所以我们看这个if里面的代码:return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);

    static void
    _dispatch_barrier_sync_f(dispatch_queue_t dq, void *ctxt,
            dispatch_function_t func, uintptr_t dc_flags)
    {
        _dispatch_barrier_sync_f_inline(dq, ctxt, func, dc_flags);
    }
    
    _dispatch_barrier_sync_f_inline

    因为我们上面已经看到,死锁会走到_dispatch_sync_f_slow函数,所以看这个方法我们只需要找到这个函数的调用,又回到之前调用的流程中函数,但是这次判断就会不一样了:

    _dispatch_sync_f_slow

    为了验证我们分析是正确的,首先运行看函数调用:

    image

    再看源码中__DISPATCH_WAIT_FOR_QUEUE__方法:

    __DISPATCH_WAIT_FOR_QUEUE__
    #define DISPATCH_CLIENT_CRASH(c, x) do { \
            _dispatch_set_crash_log_cause_and_message((c), \
                    "BUG IN CLIENT OF LIBDISPATCH: " x); \
            _dispatch_hardware_crash(); \
        } while (0)
    

    就可以看到源码分析中输出的message和我们运行汇编message一模一样,所以分析正确!其实在messag里面已经说明了什么情况会死锁,dispatch_sync调用的队列,已经是当前的线程中了。

    流程没问题了,虽然message我们可以看出端倪,但是我们还是想知道代码如何判断会产生死锁,也就是说,什么条件下会走到这些代码呢。最重要的还是要看__DISPATCH_WAIT_FOR_QUEUE__方法里面的判断

    if (unlikely(_dq_state_drain_locked_by(dq_state, dsc->dsc_waiter))) {
        DISPATCH_CLIENT_CRASH((uintptr_t)dq_state,
                "dispatch_sync called on queue "
                "already owned by current thread");
    }
    

    当条件满足时,才会走到下面的方法,我们现在就分析这个条件。
    dsc->dsc_waiter就是当前线程的iddsc是上面参数传过来的,

    image
    #define _dispatch_tid_self()        ((dispatch_tid)(_dispatch_get_tsd_base()->tid))
    

    dq_state = _dispatch_wait_prepare(dq);代表当前队列的状态。

    _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;
    }
    

    这里面的掩码DLOCK_OWNER_MASK是很大的值#define DLOCK_OWNER_MASK ((dispatch_lock)0xfffffffc),也就是前面如果是0的话,返回YES就会造成死锁,也就是statetid相同时,会死锁,也就是说当前线程要等待,然后你又调用当前线程执行,就会造成死锁。

    单利原理分析

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"once");
    });
    

    gcd单利写法如上面代码,底层是怎么实现的呢,我们现在来研究一下。
    在源码中全局搜索dispatch_once找到它的实现:

    void
    dispatch_once(dispatch_once_t *val, dispatch_block_t block)
    {
        dispatch_once_f(val, block, _dispatch_Block_invoke(block));
    }
    
    dispatch_once_f
    typedef struct dispatch_gate_s {
        dispatch_lock dgl_lock;
    } dispatch_gate_s, *dispatch_gate_t;
    
    typedef struct dispatch_once_gate_s {
        union {
            dispatch_gate_s dgo_gate;
            uintptr_t dgo_once;
        };
    } dispatch_once_gate_s, *dispatch_once_gate_t;
    

    判断能否进入函数执行:

    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);
    }
    

    调用函数并广播:

    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);
    }
    

    流程总结如下:

    1. 先试类型强制转换成dispatch_once_gate_t,标记这个是否已经做过和一些锁
    2. 判断dgo_once等于DLOCK_ONCE_DONE,相当于做过,直接return
    3. 如果尝试加锁失败,直接把dgo_once设置成DLOCK_ONCE_DONE
    4. 判断l没有被加锁,也就是没有别的线程操作,调用函数并且广播
    5. 等待其他人开锁

    相关文章

      网友评论

          本文标题:多线程(二)

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