Dispatch_async and dispatch_sync are dispatch_async and dispatch_sync are dispatch_once and semaphore functions.

GCD function reading process will involve a number of macro definitions composed of the structure of the definition, need to step by step macro expansion to better understand the code.

dispatch_once

Dispatch_once ensures that tasks are executed only once, even if multiple threads are invoked simultaneously. Often used to create singletons, swizzeld method, and other functions.

The dispatch_once function is similar to the dispatch_sync function, which calls the same function with the suffix _f.

Dispatch_once is a synchronization function that blocks the current thread until the block returns. “Dispatch_sync” and “dispatch_async” may be a bit confusing, but when you see a function with a block in it, you always want to first think about whether it will block, return immediately, or wait for the block to complete.

The block parameter of dispatch_once can only be called once globally, even in a multi-threaded environment. How to lock or block a thread when multiple threads concurrently call dispatch_once? Let’s explore…

#ifdef __BLOCKS__
void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
    dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
#endif
Copy the code

dispatch_once_t

Predicates used with the dispatch_once function must be initialized to zero. (Static and global variables default to zero.)

DISPATCH_SWIFT3_UNAVAILABLE("Use lazily initialized globals instead")
typedef intptr_t dispatch_once_t;
Copy the code

dispatch_once_gate_t

Dispatch_gate_t is a pointer to the dispatch_gate_s structure, which has only one uint32_t member variable dgl_lock.

Dispatch_once_gate_t is a pointer to the dispatch_once_gate_S structure, which contains only one union.

typedef struct dispatch_gate_s {
    // typedef uint32_t dispatch_lock;
    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;
Copy the code

DLOCK_ONCE_DONE/DLOCK_ONCE_UNLOCKED

DLOCK_ONCE_UNLOCKED corresponds to DLOCK_ONCE_DONE, indicating the status before and after dispatch_once. DLOCK_ONCE_UNLOCKED is used to indicate that dispatch_once has not been executed, and DLOCK_ONCE_DONE is used to indicate that dispatch_once has been executed.

#define DLOCK_ONCE_UNLOCKED   ((uintptr_t)0)
#define DLOCK_ONCE_DONE   (~(uintptr_t)0)
Copy the code

dispatch_once_f

The function submitted by dispatch_once_f is determined by whether the value of val (dgo_once member variable) is non-zero.

DISPATCH_NOINLINE
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
    // convert val to dispatch_once_gate_t
    dispatch_once_gate_t l = (dispatch_once_gate_t)val;

#if! DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    // atomically get l->dgo_once
    uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
    // Check whether v is DLOCK_ONCE_DONE (most likely, val has been assigned DLOCK_ONCE_DONE and func has been executed), if yes, return DLOCK_ONCE_DONE
    if (likely(v == DLOCK_ONCE_DONE)) {
        return;
    }
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    // Different decision forms
    // Determine whether v still has a lock, if so return
    if (likely(DISPATCH_ONCE_IS_GEN(v))) {
        return _dispatch_once_mark_done_if_quiesced(l, v);
    }
#endif
#endif

    // if l (l->dgo_once) is non-zero
    L ->dgo_once (DLOCK_ONCE_DONE); // If (dgo_once (DLOCK_ONCE_DONE)); Or the _dispatch_once_wait function below),
    // zero indicates that the submitted function has not been executed yet
    
    // _dispatch_once_gate_tryenter returns YES if l (l->dgo_once) is NULL (0), NO otherwise,
    // This ensures that only the first thread enters if to start executing the submitted function. The other thread then blocks the _dispatch_once_WAIT function below and waits for the wake up operation in _dispatch_once_callout.

    if (_dispatch_once_gate_tryenter(l)) {
        // execute the function submitted by dispatch_once_f
        return _dispatch_once_callout(l, ctxt, func);
    }
    
    // The thread blocks and waits for the dispatch_function_t submitted func to complete or internally determines that the func has completed.
    _dispatch_once_callout will wake up the blocked thread after func is executed
    return _dispatch_once_wait(l);
}
Copy the code

The functions that are nested in the dispatch_once_f function are analyzed below.

_dispatch_once_gate_tryenter

_dispatch_once_gate_tryenter atomicity check whether l (l->dgo_once) is non-zero. Non-zero indicates that the dispatch_once_f function has been executed (or is being executed). Zero indicates that the function has not been executed.

If l (l->dgo_once) is zero, then _dispatch_once_gate_tryenter assigns l (l->dgo_once) to the ID of the current thread. After the dispatch_once_f dispatch_once_gate_broadcast function is executed, l (L -> dGO_once) is assigned to DLOCK_ONCE_DONE. (_dispatch_lock_value_for_self retrieves the ID of the current thread)

There is also a point where l (l->dgo_once) is assigned the ID of the current thread every time _dispatch_once_gate_tryenter is executed. It corresponds to the judgment of v == value_self in the _dispatch_once_gate_broadcast function below. If dispatch_once_f is called by a single thread, there is no other thread blocking, so there is no need to wake up the operation of the thread. In a multithreaded environment, the _dispatch_once_gate_tryenter function is called multiple times, and the v is updated each time. Within the _dispatch_once_gate_broadcast function, Value_self is the ID of the thread that originally executed the submitted function, and v is the ID of another thread that is blocking the execution of the submitted function, so it needs to wake up after the submitted function completes.

DISPATCH_ALWAYS_INLINE
static inline bool
_dispatch_once_gate_tryenter(dispatch_once_gate_t l)
{
    Os_atomic_cmpxchg Specifies whether l-> dGO_once is equal to DLOCK_ONCE_UNLOCKED (indicating that the value is 0). If the value is 0, the value is assigned to the CURRENT thread ID
    // If &l->dgo_once is NULL (0) return YES, otherwise return NO
    return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED, (uintptr_t)_dispatch_lock_value_for_self(), relaxed);
}
Copy the code
os_atomic_cmpxchg

The p variable is equivalent to the atomic_t PTR pointer used to obtain the current memory access restriction rule m value, used to compare the old value e, if equal to the new value V, if not equal to the value of p memory space assigned to e.

#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); })
Copy the code
_dispatch_lock_value_for_self

_dispatch_lock_value_for_self retrieves the ID of the current thread, which is assigned to val (dgo_once member variable). Val will be assigned the thread ID until the execution of the dispatch_once_f submitted function is complete, and DLOCK_ONCE_DONE when the execution of the dispatch_once_f submitted function is complete, Static dispatch_once_t onceToken for dispatch_once; The onceToken value is 0 before dispatch_once. The initial onceToken value must be 0. Otherwise, blocks in dispatch_once will not be executed. Prints onceToken, which has a value of -1, if we manually change onceToken to 0 then we can execute the block submitted by dispatch_once again).

DISPATCH_ALWAYS_INLINE
static inline dispatch_lock
_dispatch_lock_value_for_self(void)
{
    // _dispatch_tid_self() is the ID of the current thread
    return _dispatch_lock_value_from_tid(_dispatch_tid_self());
}
Copy the code
_dispatch_lock_value_from_tid

The _dispatch_LOCK_value_FROm_TID function contains only an and operation.

DISPATCH_ALWAYS_INLINE
static inline dispatch_lock
_dispatch_lock_value_from_tid(dispatch_tid tid)
{
    // #define DLOCK_OWNER_MASK ((dispatch_lock)0xfffffffc)
    return tid & DLOCK_OWNER_MASK;
}
Copy the code

The _dispatch_once_gate_tryenter function returns the value of the _dispatch_once_gate_tryenter function. There are two branches, one for the commit function, and one for the commit function that has been executed. Execute the following _dispatch_once_wait(L) to block the thread (the submitted function is executing) or to terminate the function call (the submitted function has already executed). If the dispatch_once function is executed and another thread is called, the next thread will block and wait, and the blocked thread will wake up when the submitted function completes.

_dispatch_once_callout

The _dispatch_once_callout function does two things, one is to call the submitted function and the other is to issue a broadcast to wake up the blocked waiting thread.

// return _dispatch_once_callout(l, ctxt, func);

DISPATCH_NOINLINE
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
        dispatch_function_t func)
{
    Function f(CTXT), (func(CTXT))
    _dispatch_client_callout(ctxt, func);
    
    // Broadcast to wake up blocked threads
    _dispatch_once_gate_broadcast(l);
}
Copy the code
_dispatch_client_callout

Execute block, that is, call f(CTXT) function.

Thread-specific Data (TSD) is thread-specific data that contains TSD functions for storing and retrieving data from Thread objects. For example, the CFRunLoopGetMain() function calls _CFRunLoopGet0(), where the TSD interface is used to get runloop objects from thread.

Here _dispatch_get_tsd_base() also gets the thread’s private data. While _dispatch_get_UNwind_tsd, _dispatch_set_UNwind_tsd and _dispatch_free_UNwind_tsd seem to be thread-safe for f(CTXT) execution.

DISPATCH_NOINLINE
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); // Execute the function
    
    _dispatch_free_unwind_tsd();
    _dispatch_set_unwind_tsd(u);
}
Copy the code
_dispatch_once_gate_broadcast

In the _dispatch_once_gate_broadcast function, the atomicity of L (dGO_once member variable) is assigned to DLOCK_ONCE_DONE, indicating that the submitted function has been executed globally only once, and then an optimization call is made. Value_self = value_self = value_self = value_self = value_self = value_self = value_self = value_self If the value of v is not equal, it means that another thread is coming in at the same time that the function sent by dispatch_once_f is executing. If the value of v is the ID of the second thread, then _dispatch_gate_broadcast_slow is required to wake up the blocked thread.

DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_once_gate_broadcast(dispatch_once_gate_t l)
{
    // Get the ID of the current thread
    dispatch_lock value_self = _dispatch_lock_value_for_self();
    
    uintptr_t v;
    
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    v = _dispatch_once_mark_quiescing(l);
#else
    // Atomically set l (dGO_once) to DLOCK_ONCE_DONE and return the original value of L (dGO_once)
    v = _dispatch_once_mark_done(l);
#endif
    
    // Why is there a sentence here? In fact, this is a hidden deep optimization....
    
    If dispatch_once is performed by a single thread, v equals value_self, return.
    // If dispatch_once is performed by multiple threads, v may not equal value_self, and the following _dispatch_gate_broadcast_slow is required to wake up the blocked thread.
    if (likely((dispatch_lock)v == value_self)) return;
    
    // Wake up the blocked thread
    _dispatch_gate_broadcast_slow(&l->dgo_gate, (dispatch_lock)v);
}
Copy the code
_dispatch_once_mark_done

The atomic setting of &dgo->dgo_once is DLOCK_ONCE_DONE and the old value of &dgo->dgo_once is returned, at which point dispatch_once is marked as having been performed.

DISPATCH_ALWAYS_INLINE
static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
    // Return the old value of &dgo->dgo_once
    return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}
Copy the code
_dispatch_gate_broadcast_slow
void
_dispatch_gate_broadcast_slow(dispatch_gate_t dgl, dispatch_lock cur)
{
    if (unlikely(! _dispatch_lock_is_locked_by_self(cur))) {DISPATCH_CLIENT_CRASH(cur, "lock not owned by current thread");
    }

#if HAVE_UL_UNFAIR_LOCK
    // Wake up the thread
    _dispatch_unfair_lock_wake(&dgl->dgl_lock, ULF_WAKE_ALL);
#elif HAVE_FUTEX
    // Wake up the thread
    _dispatch_futex_wake(&dgl->dgl_lock, INT_MAX, FUTEX_PRIVATE_FLAG);
#else
    (void)dgl;
#endif
}
Copy the code
_dispatch_unfair_lock_wake
static void
_dispatch_unfair_lock_wake(uint32_t *uaddr, uint32_t flags)
{
    return _dlock_wake(uaddr, flags | UL_UNFAIR_LOCK);
}

static void
_dlock_wake(uint32_t *uaddr, uint32_t flags)
{
    int rc = __ulock_wake(flags | ULF_NO_ERRNO, uaddr, 0); // __ulock_wake is mandatory
    if (rc == 0 || rc == -ENOENT) return;
    DISPATCH_INTERNAL_CRASH(-rc, "ulock_wake() failed");
}
Copy the code

This completes the process of the single thread executing dispatch_once_f for the first time. Let’s take a look at another super important branch, _dispatch_once_wait(L).

_dispatch_once_wait

For the _dispatch_once_WAIT function, os_ATOMIC_RMW_loop is used to obtain the status from the underlying operating system. Run the OS_ATOMic_RMW_loop_give_UP command to return the status. If it changes to DLOCK_ONCE_DONE, call OS_atomic_RMW_loop_give_UP (return) to exit the wait.

void
_dispatch_once_wait(dispatch_once_gate_t dgo)
{
    // Get the ID of the current thread
    dispatch_lock self = _dispatch_lock_value_for_self();
    uintptr_t old_v, new_v;
    
    / / remove dgl_lock
    dispatch_lock *lock = &dgo->dgo_gate.dgl_lock;
    uint32_t timeout = 1;

    // Enter an infinite loop
    for (;;) {
        os_atomic_rmw_loop(&dgo->dgo_once, old_v, new_v, relaxed, {
            if (likely(old_v == DLOCK_ONCE_DONE)) { // old_v is set to DLOCK_ONCE_DONE in _dispatch_once_mark_done
            
                // ⬇️⬇️ general branch, dispatch_once_f the function submitted has been executed, then directly terminate the function execution
                os_atomic_rmw_loop_give_up(return);
            }
            
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
            if (DISPATCH_ONCE_IS_GEN(old_v)) {
                os_atomic_rmw_loop_give_up({
                    os_atomic_thread_fence(acquire);
                    return _dispatch_once_mark_done_if_quiesced(dgo, old_v);
                });
            }
#endif
            // #define DLOCK_WAITERS_BIT ((dispatch_lock)0x00000001)
            
            new_v = old_v | (uintptr_t)DLOCK_WAITERS_BIT;
            if (new_v == old_v) os_atomic_rmw_loop_give_up(break); // Break the loop
        });
        if (unlikely(_dispatch_lock_is_locked_by((dispatch_lock)old_v, self))) {
            DISPATCH_CLIENT_CRASH(0."trying to lock recursively");
        }
#if HAVE_UL_UNFAIR_LOCK
        _dispatch_unfair_lock_wait(lock, (dispatch_lock)new_v, 0,
                DLOCK_LOCK_NONE);
#elif HAVE_FUTEX
        _dispatch_futex_wait(lock, (dispatch_lock)new_v, NULL,
                FUTEX_PRIVATE_FLAG);
#else
        _dispatch_thread_switch(new_v, flags, timeout++);
#endif
        (void)timeout; }}Copy the code
os_atomic_rmw_loop

Os_atomic_rmw_loop is a macro definition. The __VA_ARGS__ parameter represents operations in the do while loop.

#define os_atomic_rmw_loop(p, ov, nv, m, ...)  ({ \
    bool _result = false; \
    __typeof__(p) _p = (p); \
    ov = os_atomic_load(_p, relaxed); \ // The atom reads &dgo->dgo_once
    do { \
        __VA_ARGS__; \
        _result = os_atomic_cmpxchgvw(_p, ov, nv, &ov, m); The \}while (unlikely(! _result)); \ _result; The \})Copy the code

Dgo ->dgo_once set DLOCK_ONCE_DONE to DLOCK_ONCE_DONE I saw some articles saying that _dispatch_thread_semaphore_wait was used to block the thread, which has been updated.

dispatch_semaphore

Dispatch_semaphore is the most common operation in GCD and is usually used to ensure multithreaded security of resources and to control the number of concurrent tasks. Its essence is actually based on the Mach kernel semaphore interface to implement.

dispatch_semaphore_s

Dispatch_semaphore_t is a pointer to the dispatch_semaphore_s structure. First, take a look at the underlying data structure.

struct dispatch_queue_s;

DISPATCH_CLASS_DECL(semaphore, OBJECT);
struct dispatch_semaphore_s {
    DISPATCH_OBJECT_HEADER(semaphore);
    long volatile dsema_value;
    long dsema_orig;
    _dispatch_sema4_t dsema_sema;
};
Copy the code

The macro definition expands as follows:

struct dispatch_semaphore_s;

// OS_OBJECT_CLASS_DECL(dispatch_semaphore, DISPATCH_OBJECT_VTABLE_HEADER(dispatch_semaphore))

struct dispatch_semaphore_extra_vtable_s {
    unsigned long const do_type;
    void (*const do_dispose)(struct dispatch_semaphore_s *, bool *allow_free);
    size_t (*const do_debug)(struct dispatch_semaphore_s *, char *, size_t);
    void (*const do_invoke)(struct dispatch_semaphore_s *, dispatch_invoke_context_t.dispatch_invoke_flags_t);
};

struct dispatch_semaphore_vtable_s {
    // _OS_OBJECT_CLASS_HEADER();
    void (*_os_obj_xref_dispose)(_os_object_t);
    void (*_os_obj_dispose)(_os_object_t);
    
    struct dispatch_semaphore_extra_vtable_s _os_obj_vtable;
};

// OS_OBJECT_CLASS_SYMBOL(dispatch_semaphore)

extern const struct dispatch_semaphore_vtable_s _OS_dispatch_semaphore_vtable;
extern const struct dispatch_semaphore_vtable_s OS_dispatch_semaphore_class __asm__(" __"OS_STRINGIFY(dispatch_semaphore) "_vtable");

struct dispatch_semaphore_s {
    struct dispatch_object_s _as_do[0].
    struct _os_object_s _as_os_obj[0].
    
    const struct dispatch_semaphore_vtable_s *do_vtable; /* must be pointer-sized */
    
    int volatile do_ref_cnt;
    int volatile do_xref_cnt;
    
    struct dispatch_semaphore_s *volatile do_next;
    struct dispatch_queue_s *do_targetq;
    void *do_ctxt;
    void *do_finalizer;
    
    // You can see that the upper part is the same as the other GCD objects. After all, we all inherit from dispatch_object_s. The main point is the following two new member variables
    // DsemA_value and dsemA_ORIg are the key tasks of semaphore execution. For each dispatch_semaphore_WAIT operation, the value of dsemA_value is reduced once
    
    long volatile dsema_value;
    long dsema_orig;
    _dispatch_sema4_t dsema_sema;
};
Copy the code

The contents of the DISPATCH_VTABLE_INSTANCE macro definition package are the initialization of the contents of the dispatch_semaphoRE_vtable_s structure, that is, some operation functions of the semaphore. (The Dispatch Object Cluster section of the init.c file contains the initialization of many GCD object operation functions.)

// Assign the corresponding member variable in the dispatch_semaphoRE_EXTRA_vtable_s
DISPATCH_VTABLE_INSTANCE(semaphore, .do_type = DISPATCH_SEMAPHORE_TYPE, .do_dispose = _dispatch_semaphore_dispose, .do_debug = _dispatch_semaphore_debug, .do_invoke = _dispatch_object_no_invoke, ); ⬇️ (macro expansion)DISPATCH_VTABLE_SUBCLASS_INSTANCE(Semaphore, semaphore, __VA_ARGS__) ⬇️ (macro expansion)OS_OBJECT_VTABLE_SUBCLASS_INSTANCE(dispatch_semaphore, dispatch_semaphore, _dispatch_xref_dispose, _dispatch_dispose, __VA_ARGS__) ⬇️ (macro expansion)const struct dispatch_semaphore_vtable_s OS_OBJECT_CLASS_SYMBOL(dispatch_semaphore) = { \ ._os_obj_xref_dispose = _dispatch_xref_dispose, \ ._os_obj_dispose = _dispatch_dispose, \._os_OBJ_vtable = {__VA_ARGS__}, \} ⬇️ (macro expansion)const struct dispatch_semaphore_vtable_s OS_dispatch_semaphore_class = {
    ._os_obj_xref_dispose = _dispatch_xref_dispose,
    ._os_obj_dispose = _dispatch_dispose,
    ._os_obj_vtable = { 
        .do_type        = DISPATCH_SEMAPHORE_TYPE, / / type
        .do_dispose     = _dispatch_semaphore_dispose, // Dispose function assignment
        .do_debug       = _dispatch_semaphore_debug, / / the debug assignment
        .do_invoke      = _dispatch_object_no_invoke, // Invoke is assigned}},Copy the code

Dispatch_semaphore_s: Dsema_orig is the initial value of the semaphore, dSEMA_value is the current value of the semaphore, and the relevant API of the semaphore realizes its function by operating dsemA_value. _dispatch_SEMa4_t is the structure of the semaphore.

_dispatch_sema4_t/_DSEMA4_POLICY_FIFO

_dispatch_sema4_t uses different types on different platforms and environments. (The exact type is not found in the libdispatch source code)

_DSEMA4_POLICY_FIFO is used in the _dispatch_SEMa4_init function call below.

#if USE_MACH_SEM
  typedef semaphore_t _dispatch_sema4_t;
  #define _DSEMA4_POLICY_FIFO  SYNC_POLICY_FIFO
#elif USE_POSIX_SEM
  typedef sem_t _dispatch_sema4_t;
  #define _DSEMA4_POLICY_FIFO 0
#elif USE_WIN32_SEM
  typedef HANDLE _dispatch_sema4_t;
  #define _DSEMA4_POLICY_FIFO 0
#else
#error "port has to implement _dispatch_sema4_t"
#endif
Copy the code

Here is a look at the dispatch_semaphore_s API source code implementation.

dispatch_semaphore_create

Dispatch_semaphore_create Creates a new count semaphore with an initial long value.

Passing the value to zero is useful when two threads need to coordinate the completion of a particular event. Passing a value greater than zero is useful for managing finite resource pools whose size is equal to this value (e.g., we have multiple files to download from the server, Then limit the number of dispatch_semaphore downloads to five threads (dispatch_semaphore_create(5)).

Parameter value: The starting value of the semaphore. Passing a value less than zero results in NULL being returned. Return value result: Newly created semaphore, NULL on failure.

dispatch_semaphore_t
dispatch_semaphore_create(long value)
{
    // pointer to the dispatch_semaphore_s structure
    dispatch_semaphore_t dsema;

    // If the internal value is negative, then the absolute of the value is equal
    // to the number of waiting threads. 
    // Therefore it is bogus to initialize the semaphore with a negative value.
    
    if (value < 0) {
        // #define DISPATCH_BAD_INPUT ((void *_Nonnull)0)
        // If value is less than 0, 0 is returned
        return DISPATCH_BAD_INPUT;
    }

    / / DISPATCH_VTABLE (semaphore) ➡ ️ & OS_dispatch_semaphore_class
    OS_dispatch_semaphore_class = &os_dispatch_semaphore_class;
    OS_dispatch_semaphore_class Specifies the dispatch_SEMAPHORE_T callback function, such as dispose function _dispatch_semaphore_dispose
    dsema = _dispatch_object_alloc(DISPATCH_VTABLE(semaphore),
            sizeof(struct dispatch_semaphore_s));
    
    // #if DISPATCH_SIZEOF_PTR == 8
    // // the bottom nibble must not be zero, the rest of the bits should be random we sign extend the 64-bit version so that a better instruction encoding is generated on Intel
    // #define DISPATCH_OBJECT_LISTLESS ((void *)0xffffffff89abcdef)
    // #else
    // #define DISPATCH_OBJECT_LISTLESS ((void *)0x89abcdef)
    // #endif
    
    // Represents the next node in the list
    dsema->do_next = DISPATCH_OBJECT_LISTLESS;
    
    // Target queues (default queues taken from the global queue array _dispatch_root_queues)
    dsema->do_targetq = _dispatch_get_default_queue(false);
    
    dsema->dsema_value = value; // Current value (current is the initial value)
    
    // does _DSEMA4_POLICY_FIFO represent a first-in, first-out policy?
    _dispatch_sema4_init(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
    
    dsema->dsema_orig = value; / / initial value
    return dsema;
}
Copy the code

_dispatch_get_default_queue

Overcommit = com.apple.root.default-qos. Overcommit = com.apple.root.default-qos. If false is set to com.apple.root.default-qos queue.

#define _dispatch_get_default_queue(overcommit) \
        _dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_QOS + \
                !!(overcommit)]._as_dq
Copy the code

_dispatch_object_alloc

Allocates space for and initializes GCD objects.

void *
_dispatch_object_alloc(const void *vtable, size_t size)
{
#if OS_OBJECT_HAVE_OBJC1
    const struct dispatch_object_vtable_s* _vtable = vtable;
    dispatch_object_t dou;
    dou._os_obj = _os_object_alloc_realized(_vtable->_os_obj_objc_isa, size);
    dou._do->do_vtable = vtable;
    return dou._do;
#else
    return _os_object_alloc_realized(vtable, size);
#endif
}
Copy the code

The _OS_OBJect_alloc_realized function is called internally. Let’s look at its definition.

_os_object_alloc_realized

The core applies for space in the calloc function and assigns values.

inline _os_object_t
_os_object_alloc_realized(const void *cls, size_t size)
{
    _os_object_t obj;
    dispatch_assert(size >= sizeof(struct _os_object_s));
    
    // The while loop is just for space success, the core is still in the calloc function
    while (unlikely(! (obj =calloc(1u, size)))) {
        _dispatch_temporary_resource_shortage();
    }
    
    obj->os_obj_isa = cls;
    return obj;
}
Copy the code

dispatch_semaphore_wait

Dispatch_semaphore_wait Waits for (reduces) the semaphore.

long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
    // Atom operation dsema member variable dsemA_value value minus 1
    long value = os_atomic_dec2o(dsema, dsema_value, acquire);
    
    // Return if the value is still greater than or equal to 0
    if (likely(value >= 0)) {
        return 0;
    }
    
    // If less than 0, block wait by calling _dispatch_semaphoRE_wait_slow
    return _dispatch_semaphore_wait_slow(dsema, timeout);
}
Copy the code

Decrement the count semaphore. If the result value is less than zero, this function waits for the signal to appear and then returns. (You can decrease the total semaphore by 1, and wait until the total number of signals is less than zero, otherwise it will execute normally.) Dsema: Semaphore, the result of passing NULL in this parameter is undefined. Timeout: when to timeout (dispatch_time). For convenience, there are DISPATCH_TIME_NOW and DISPATCH_TIME_FOREVER constants. The function returns the value result, which returns zero on success or non-zero (_DSEMA4_TIMEOUT) if a timeout occurs.

os_atomic_dec2o

Os_atomic_dec2o is the encapsulation of atomic operation-1.

#define os_atomic_dec2o(p, f, m) \
        os_atomic_sub2o(p, f, 1, m)
        
#define os_atomic_sub2o(p, f, v, m) \
        os_atomic_sub(&(p)->f, (v), m)
        
#define os_atomic_sub(p, v, m) \
        _os_atomic_c11_op((p), (v), m, sub, -)
        
#define _os_atomic_c11_op(p, v, m, o, op) \
        ({ _os_atomic_basetypeof(p) _v = (v), _r = \
        atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), _v, \
        memory_order_##m); (__typeof__(_r))(_r op _v); })
Copy the code

_dispatch_semaphore_wait_slow

DISPATCH_NOINLINE
static long
_dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema,
        dispatch_time_t timeout)
{
    long orig;
    
    // Assign &dsema->dsema_sema
    _dispatch_sema4_create(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
    
    switch (timeout) {
    
    // Call _dispatch_sema4_timedWait if timeout is a specific time
    default:
        if(! _dispatch_sema4_timedwait(&dsema->dsema_sema, timeout)) {break;
        }
        // Fall through and try to undo what the fast path did to dsema->dsema_value
    // If the timeout parameter is DISPATCH_TIME_NOW
    case DISPATCH_TIME_NOW:
        orig = dsema->dsema_value;
        while (orig < 0) {
        
            // dsemA_value + 1 cancellations the decrement of 1 in dispatch_semaphore_wait
            if (os_atomic_cmpxchgvw2o(dsema, dsema_value, orig, orig + 1,
                    &orig, relaxed)) {
                // Return timeout
                return_DSEMA4_TIMEOUT(); }}// Another thread called semaphore_signal().
        // Fall through and drain the wakeup.
    
    // If the timeout parameter is DISPATCH_TIME_FOREVER, call _dispatch_sema4_wait and wait until signal is received
    case DISPATCH_TIME_FOREVER:
        _dispatch_sema4_wait(&dsema->dsema_sema);
        break;
    }
    
    return 0;
}
Copy the code
_dispatch_sema4_create

&dsema-> dsemA_sema is assigned if NULL.

DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_sema4_create(_dispatch_sema4_t *sema, int policy)
{
    // #define _dispatch_sema4_is_created(sema) (*(sema) ! = MACH_PORT_NULL)
    
    // If sema is NULL, call _dispatch_sema4_create_slow to assign sema
    if(! _dispatch_sema4_is_created(sema)) {// Read or create from cache_dispatch_sema4_create_slow(sema, policy); }}Copy the code

If DISPATCH_USE_OS_SEMAPHORE_CACHE is true and policy is _DSEMA4_POLICY_FIFO, Os_get_cached_semaphore assigns a _dispatch_SEMa4_t value to S4 from the cache. Otherwise, semaphore_CREATE assigns a new _dispatch_SEMa4_t value to S4.

void
_dispatch_sema4_create_slow(_dispatch_sema4_t *s4, int policy)
{
    semaphore_t tmp = MACH_PORT_NULL;

    _dispatch_fork_becomes_unsafe();

    // lazily allocate the semaphore port

    // Someday:
    // 1) Switch to a doubly-linked FIFO in user-space.
    // 2) User-space timers for the timeout.

#if DISPATCH_USE_OS_SEMAPHORE_CACHE
    if (policy == _DSEMA4_POLICY_FIFO) {
        tmp = (_dispatch_sema4_t)os_get_cached_semaphore(a);// if S4 equals MACH_PORT_NULL, TMP is assigned to it
        if (!os_atomic_cmpxchg(s4, MACH_PORT_NULL, tmp, relaxed)) {
        
            // If S4 is not MACH_PORT_NULL, add it to the cache
            os_put_cached_semaphore((os_semaphore_t)tmp);
        }
        return;
    }
#endif
    
    New kern_return_t / /
    kern_return_t kr = semaphore_create(mach_task_self(), &tmp, policy, 0);
    DISPATCH_SEMAPHORE_VERIFY_KR(kr);

    // Atom assignment
    if (!os_atomic_cmpxchg(s4, MACH_PORT_NULL, tmp, relaxed)) {
        kr = semaphore_destroy(mach_task_self(), tmp);
        DISPATCH_SEMAPHORE_VERIFY_KR(kr); }}Copy the code
_dispatch_sema4_wait

When timeout is DISPATCH_TIME_FOREVER, the do while loop waits until the sema value is changed to something other than KERN_ABORTED.

void
_dispatch_sema4_wait(_dispatch_sema4_t *sema)
{
    kern_return_t kr;
    do {
        kr = semaphore_wait(*sema);
    } while (kr == KERN_ABORTED);
    
    DISPATCH_SEMAPHORE_VERIFY_KR(kr);
}
Copy the code
_dispatch_sema4_timedwait

When timeout is a specified time, the loop waits until timeout or a signal is sent and the semA value is modified.

bool
_dispatch_sema4_timedwait(_dispatch_sema4_t *sema, dispatch_time_t timeout)
{
    mach_timespec_t _timeout;
    kern_return_t kr;

    do {
        // Take the time difference
        uint64_t nsec = _dispatch_timeout(timeout);
        
        _timeout.tv_sec = (__typeof__(_timeout.tv_sec))(nsec / NSEC_PER_SEC);
        _timeout.tv_nsec = (__typeof__(_timeout.tv_nsec))(nsec % NSEC_PER_SEC);
        
        kr = semaphore_timedwait(*sema, _timeout);
    } while (unlikely(kr == KERN_ABORTED));

    if (kr == KERN_OPERATION_TIMED_OUT) {
        return true;
    }
    
    DISPATCH_SEMAPHORE_VERIFY_KR(kr);
    
    return false;
}
Copy the code

The Mach kernel semaphore_WAIT and semaphore_timedWait were called to perform wait operations. Therefore, GCD’s semaphore is actually implemented based on the Mach kernel’s semaphore interface. The semaphoRE_timedWait function specifies a timeout.

dispatch_semaphore_signal

Dispatch_semaphore_signal Sends (increases) signals. If the previous value is less than zero, this function wakes up the waiting thread before returning. If the thread is awakened, this function returns a non-zero value. Otherwise, zero is returned.

long
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
    // Atom operation dsema member variable dsemA_value value increment 1
    long value = os_atomic_inc2o(dsema, dsema_value, release);
    
    if (likely(value > 0)) {
        // If value is greater than 0, there are no threads to wake up
        return 0;
    }
    
    // If the value exceeds the value of LONG_MIN, then crash
    if (unlikely(value == LONG_MIN)) {
        DISPATCH_CLIENT_CRASH(value,
                "Unbalanced call to dispatch_semaphore_signal()");
    }
    
    // If value is less than or equal to 0, there is a thread to wake up
    return _dispatch_semaphore_signal_slow(dsema);
}
Copy the code

The value == LONG_MIN test does not crash.

dispatch_semaphore_t semaphore = dispatch_semaphore_create(LONG_MAX);
dispatch_semaphore_signal(semaphore);

// Console prints
(lldb) po semaphore
<OS_dispatch_semaphore: semaphore[0x600000ed79d0] = { xref = 1, ref = 1, port = 0x0, value = 9223372036854775807, orig = 9223372036854775807 }>

(lldb) po semaphore
<OS_dispatch_semaphore: semaphore[0x600000ed79d0] = { xref = 1, ref = 1, port = 0x0, value = - 9223372036854775808., orig = 9223372036854775807}> ⬅️ value = LONG_MINCopy the code

os_atomic_inc2o

Os_atomic_inc2o is the +1 encapsulation for atomic operations.

#define os_atomic_inc2o(p, f, m) \
        os_atomic_add2o(p, f, 1, m)

#define os_atomic_add2o(p, f, v, m) \
        os_atomic_add(&(p)->f, (v), m)
        
#define os_atomic_add(p, v, m) \
        _os_atomic_c11_op((p), (v), m, add, +)  
        
#define _os_atomic_c11_op(p, v, m, o, op) \
        ({ _os_atomic_basetypeof(p) _v = (v), _r = \
        atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), _v, \
        memory_order_##m); (__typeof__(_r))(_r op _v); })
Copy the code

_dispatch_semaphore_signal_slow

An internal call to _dispatch_sema4_signal(& Dsema -> dsemA_sema, 1) wakes up a thread.

DISPATCH_NOINLINE
long
_dispatch_semaphore_signal_slow(dispatch_semaphore_t dsema)
{
    _dispatch_sema4_create(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
    
    // count passes 1 to wake up a thread
    _dispatch_sema4_signal(&dsema->dsema_sema, 1);
    return 1;
}
Copy the code
_dispatch_sema4_signal

Semaphore_signal wakes up a thread waiting in semaphore_wait. If there are multiple waiting threads, wake up based on thread priority.

void
_dispatch_sema4_signal(_dispatch_sema4_t *sema, long count)
{
    do {
        // semaphore_signal wakes up the thread
        kern_return_t kr = semaphore_signal(*sema);
        DISPATCH_SEMAPHORE_VERIFY_KR(kr);
    } while (--count);
}
Copy the code

Let’s look at the last semaphore related function in the semaphore.c file.

_dispatch_semaphore_dispose

The semaphore destruction function.

void
_dispatch_semaphore_dispose(dispatch_object_t dou,
        DISPATCH_UNUSED bool *allow_free)
{
    dispatch_semaphore_t dsema = dou._dsema;

    If dsemA_value is less than dsemA_ORIg, it indicates that the semaphore is still in use and cannot be destroyed
    // dispatch_semaphore_t sema = dispatch_semaphore_create(1); // create value = 1, orig = 1
    // dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); // value = 0, orig = 1
    // sema = dispatch_semaphore_create(1); // Assignment causes dispatch_semaphoRE_s to be released, but orig is 1 and value is 0
    
    if (dsema->dsema_value < dsema->dsema_orig) {
        DISPATCH_CLIENT_CRASH(dsema->dsema_orig - dsema->dsema_value,
                "Semaphore object deallocated while in use");
    }

    _dispatch_sema4_dispose(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
}
Copy the code

_dispatch_sema4_dispose

DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_sema4_dispose(_dispatch_sema4_t *sema, int policy)
{
    // Call _dispatch_sema4_dispose_slow if sema exists
    if(_dispatch_sema4_is_created(sema)) { _dispatch_sema4_dispose_slow(sema, policy); }}Copy the code
_dispatch_sema4_dispose_slow

If DISPATCH_USE_OS_SEMAPHORE_CACHE is true and policy is _DSEMA4_POLICY_FIFO, os_PUT_cacheD_semaphore is called to place sema in the cache. The Mach kernel’s semaphore_destroy function was called to destroy the semaphore.

void
_dispatch_sema4_dispose_slow(_dispatch_sema4_t *sema, int policy)
{
    semaphore_t sema_port = *sema;
    *sema = MACH_PORT_DEAD;
    
#if DISPATCH_USE_OS_SEMAPHORE_CACHE
    if (policy == _DSEMA4_POLICY_FIFO) {
    
        // Put it in the cache
        return os_put_cached_semaphore((os_semaphore_t)sema_port);
    }
#endif

    // Call semaphore_destroy
    kern_return_t kr = semaphore_destroy(mach_task_self(), sema_port);
    DISPATCH_SEMAPHORE_VERIFY_KR(kr);
}
Copy the code

The source code of the common semaphore related API is finished, mainly according to the DSEMA_value to block and wake up the thread.

Refer to the link

Reference link :🔗

  • Libdispatch Apple source code
  • GCD source code analysis 1 — the opening chapter
  • Dig up the libDispatch source code
  • GCD source code analysis
  • A few things about GCD development
  • GCD in-depth understanding: Part ONE
  • Dispatch_once,
  • Transparent federation type
  • Perversion of libDispatch -dispatch_object_s
  • The basics of GCD
  • Swift multithreading from the source analysis – DispatchGroup
  • GCD source code analysis (a)
  • CD- source code analysis
  • GCD underlying source code analysis
  • (1) — GCD Queue
  • C/C ++: computes variable parameter macrosVA_ARGSNumber of arguments of
  • OC Low-level exploration (21)GCD asynchrony, GCD synchronization, singleton, semaphore, scheduling group, fence function and other low-level analysis
  • IOS source code parsing: GCD semaphore