iOS中GCD的dispatch_once原理

void dispatch_once_f(dispatch_once_t *val, void ctxt, dispatch_function_t func) {
struct _dispatch_once_waiter_s * volatile vval = (struct _dispatch_once_waiter_s
)val;
struct _dispatch_once_waiter_s dow = { NULL, 0 };
struct _dispatch_once_waiter_s *tail, *tmp;
_dispatch_thread_semaphore_t sema;

if (dispatch_atomic_cmpxchg(vval, NULL, &dow)) {
    _dispatch_client_callout(ctxt, func);
    tmp = dispatch_atomic_xchg(vval, DISPATCH_ONCE_DONE);
    tail = &dow;
    while (tail != tmp) {
        while (!tmp->dow_next) {
            _dispatch_hardware_pause();
        }
        sema = tmp->dow_sema;
        tmp = (struct _dispatch_once_waiter_s*)tmp->dow_next;
        _dispatch_thread_semaphore_signal(sema);
    }
} else {
    dow.dow_sema = _dispatch_get_thread_semaphore();
    for (;;) {
        tmp = *vval;
        if (tmp == DISPATCH_ONCE_DONE) {
            break;
        }
        dispatch_atomic_store_barrier();
        if (dispatch_atomic_cmpxchg(vval, tmp, &dow)) {
            dow.dow_next = tmp;
            _dispatch_thread_semaphore_wait(dow.dow_sema);
        }
    }
    _dispatch_put_thread_semaphore(dow.dow_sema);
}

}

  1. 第一次调用: 此时外部传进来的 onceToken 还是空指针,所以 vval 为 NULL,if 判断成立。首先执行 block,然后让将 vval 的值设为 DISPATCH_ONCE_DONE 表示任务已经完成,同时用 tmp 保存先前的 vval。此时,dow 也为空,因此 while 判断不成立,代码执行结束。
  2. 同一线程第二次调用: 由于 vval 已经变成了 DISPATCH_ONCE_DONE,因此 if 判断不成立,进入 else 分支的 for 循环。由于 tmp 就是 DISPATCH_ONCE_DONE,所以循环退出,没有做任何事。
  3. 多个线程同时调用: 由于 if 判断中是一个原子性操作,所以必然只有一个线程能进入 if 分支,其他的进入 else 分支。由于其他线程在调用函数时,vval 还不是 DISPATCH_ONCE_DONE,所以进入到 for 循环的后半部分。这里构造了一个链表,链表的每个节点上都调用了信号量的 wait 方法并阻塞,而在 if 分支中,则会依次遍历所有的节点并调用 signal 方法,唤醒所有等待中的信号量。

你可能感兴趣的:(iOS中GCD的dispatch_once原理)