返回

深入剖析 GCD:dispatch_once 和 dispatch_semaphore 的源码解析

IOS

在 iOS 多线程的知识体系中,GCD(Grand Central Dispatch)扮演着至关重要的角色。本文将深入探究 GCD 源码中两个关键函数:dispatch_once 和 dispatch_semaphore,为您提供对 GCD 内部机制的深入了解。

揭开 dispatch_once 的神秘面纱

dispatch_once 函数旨在确保一个任务仅被执行一次,即使在多线程并发调用时也能保证线程安全。这一特性使其在创建单例、swizzeld method 等场景中发挥着至关重要的作用。

源码解析

DISPATCH_ALWAYS_INLINE
void
_dispatch_once(dispatch_once_t *once, dispatch_block_t block)
{
    dispatch_once_gate_t gate;
    dispatch_once_callout_t callout;
    uintptr_t pre;
    uintptr_t post;

    pre = OS_atomic_load_acq(once, ONCE_GATE_PRE_BITS);
    if (!pre) {
        pre = OS_atomic_cmpxchg_acq(once, ONCE_GATE_PRE_bits(1), 0);
        if (!pre) {
            callout = (dispatch_once_callout_t)_dispatch_once_callout_with_block(block, once);
            gate = ONCE_GATE_POST_callout((uintptr_t)callout);
            if (OS_atomic_cmpxchg_acq(once, ONCE_GATE_POST_bits(gate), ONCE_GATE_PRE_BITS(1)) != ONCE_GATE_PRE_BITS(1)) {
                _dispatch_once_callout_cancel(callout);
                return;
            }
            block();
            post = OS_atomic_xchg_acq(once, gate);
            _dispatch_once_callout_release(callout, post);
            return;
        }
    }
    //其他线程调用时,等待上面的callout完成。
    while (pre != ONCE_GATE_POST_done) {
        OSSpinLockLock(&lock);
        post = OS_atomic_load_acq(once, ONCE_GATE_POST_BITS);
        if (post == ONCE_GATE_POST_done) {
            OSSpinLockUnlock(&lock);
            return;
        }
        post = OS_atomic_cmpxchg_acq(once, ONCE_GATE_POST_wait(pre), post);
        if (post != ONCE_GATE_POST_bits(pre)) {
            OSSpinLockUnlock(&lock);
            if (pre != post) {
                continue;
            }
            if (post == ONCE_GATE_POST_done) {
                return;
            }
        }
        _dispatch_thread_sleep_noninterruptible(ONCE_GATE_SLEEP_TIME);
        pre = OS_atomic_load_acq(once, ONCE_GATE_PRE_BITS);
        OSSpinLockUnlock(&lock);
    }
}

宏定义解析

为了更好的理解源码,我们需要对 dispatch_once 函数中涉及的宏定义进行解析:

  • ONCE_GATE_PRE_BITS(x) :宏定义,表示 dispatch_once 的前置位字段(用于保存执行状态)。
  • ONCE_GATE_POST_bits(x) :宏定义,表示 dispatch_once 的后置位字段(用于保存执行结果)。
  • ONCE_GATE_POST_callout(x) :宏定义,用于将 callout 转换为后置位字段的值。
  • ONCE_GATE_POST_wait(x) :宏定义,用于生成等待状态的后置位字段值。
  • ONCE_GATE_POST_done :宏定义,表示任务已执行完成。

执行流程

  1. 检查执行状态: 通过原子加载操作获取 dispatch_once 结构的前置位字段,判断任务是否已执行。
  2. 设置执行标志: 如果任务未执行,通过原子交换操作设置前置位字段为 1,表明任务已开始执行。
  3. 创建执行回调: 创建一个执行回调,并将 dispatch_once 结构的地址作为参数传递。
  4. 设置后置位字段: 将执行回调转换为后置位字段的值,并通过原子交换操作将其设置到 dispatch_once 结构中。
  5. 执行任务: 执行执行回调,完成任务。
  6. 设置完成标志: 将后置位字段设置为 ONCE_GATE_POST_done,表示任务已完成。
  7. 释放资源: 释放执行回调的资源。

dispatch_semaphore:信号量的力量

dispatch_semaphore 是一种信号量,用于同步和控制多线程之间的资源访问。它允许您指定可同时访问资源的最大线程数,从而防止资源竞争和死锁。

源码解析

DISPATCH_ALWAYS_INLINE
void
_dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
    uintptr_t state = OS_atomic_inc_acq(&dsema->dsema_state, relaxed);

    if (state < 0) {
        _dispatch_wakeup(dsema);
    }
}

宏定义解析

  • OS_atomic_inc_acq(x, relaxed) :宏定义,以松散语义原子地递增变量。

执行流程

  1. 递增状态: 使用原子递增操作递增信号量的状态(表示可访问资源的线程数)。
  2. 唤醒等待线程: 如果递增后的状态小于 0,表示有线程正在等待资源,则唤醒它们。

结语

dispatch_once 和 dispatch_semaphore 是 GCD 中两个至关重要的函数,它们使您能够控制多线程环境中的任务执行和资源访问。通过深入理解这些函数的源码,您可以更深入地了解 GCD 的工作原理,并编写出更强大、更可靠的多线程代码。

为了进一步提升文章的可读性,我加入了更多过渡词和短语,以增强文章的连贯性和流动性。此外,我还对代码段进行了排版,使其更易于阅读和理解。希望这篇文章对您有所帮助!