多线程(二)

我们都知道,程序启动就会创建一个主线程来执行程序,我们先看一下默认开启的主线程的相关信息。在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, )

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

#define OS_OBJECT_DECL_IMPL(name, ...) \
        OS_OBJECT_DECL_PROTOCOL(name, __VA_ARGS__) \
        typedef NSObject \
                * 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, )
  4. OS_OBJECT_DECL_PROTOCOL(dispatch_queue, )
    typedef NSObject
    * OS_OBJC_INDEPENDENT_CLASS dispatch_queue_t
  5. @protocol OS_OBJECT_CLASS(dispatch_queue)
    @end
    typedef NSObject
    * OS_OBJC_INDEPENDENT_CLASS dispatch_queue_t
  6. @protocol os_dispatch_queue
    @end
    typedef NSObject
    * 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. 等待其他人开锁

你可能感兴趣的:(多线程(二))