GCD ④ dispatch_once

dispatch_once (单例)

    dispatch_once 函数是保证在应用程序执行中只执行一次指定处理的 API 。通过 dispatch_once函数,该源代码即使在多线程环境下执行,也可保证百分之百安全。用 dispatch_once函数初始化就不必担心重复初始化的问题。这就是所说的单例模式,在生成单例对象时使用。使用 dispatch_once函数,则源代码写为:

static dispatch_once_t pred;
dispatch_once(&pred,^{
/*
*初始化
*/
}

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

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

#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
 uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
 //如果已经执行了
 if (likely(v == DLOCK_ONCE_DONE)) {
  return;
 }
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
 if (likely(DISPATCH_ONCE_IS_GEN(v))) {
  return _dispatch_once_mark_done_if_quiesced(l, v);
 }
#endif
#endif
  //
 if (_dispatch_once_gate_tryenter(l)) {
     //进行调用
  return _dispatch_once_callout(l, ctxt, func);
 }
 return _dispatch_once_wait(l);
}

dispatch_once_f 执行流程如下:

  1. dispatch_once,也就是静态变量转换为 dispatch_once_gate_t类型的变量 l;

  2. 通过 os_atomic_load 获取此时的任务的标识符 v,如果 v 等于 DLOCK_ONCE_DONE,表示任务已经执行过了,直接返回

  3. 如果任务执行后,但是 v 不等于 DLOCK_ONCE_DONE,则走到 _dispatch_once_mark_done_if_quiesced 函数,再次进行存储,将标识符置为 DLOCK_ONCE_DONE

         DISPATCH_ALWAYS_INLINE
    static inline void
    _dispatch_once_mark_done_if_quiesced(dispatch_once_gate_t dgo, uintptr_t gen)
    {
     if (_dispatch_once_generation() - gen >= DISPATCH_ONCE_GEN_SAFE_DELTA) {
      /*
       * See explanation above, when the quiescing counter approach is taken
       * then this store needs only to be relaxed as it is used as a witness
       * that the required barriers have happened.
       */
      os_atomic_store(&dgo->dgo_once, DLOCK_ONCE_DONE, relaxed);
     }
    }
    
  4. 通过 _dispatch_once_gate_tryenter 尝试进入任务,即解锁。

    DISPATCH_ALWAYS_INLINE
    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);
    }
    
    
  5. 调用 _dispatch_once_callout,执行 block,并将 v = DLOCK_ONCE_DONE

    DISPATCH_NOINLINE
    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);
    }
    
    DISPATCH_ALWAYS_INLINE
    static inline void
    _dispatch_once_gate_broadcast(dispatch_once_gate_t l)
    {
    
     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
    //完成
     v = _dispatch_once_mark_done(l);
    #endif
     if (likely((dispatch_lock)v == value_self)) return;
     _dispatch_gate_broadcast_slow(&l->dgo_gate, (dispatch_lock)v);
    }
    
    
  6. 如果此时有任务正在执行,则通过 _dispatch_once_wait 函数进入无限次等待,等待超时

    void
    _dispatch_once_wait(dispatch_once_gate_t dgo)
    {
     dispatch_lock self = _dispatch_lock_value_for_self();
     uintptr_t old_v, new_v;
    #if HAVE_UL_UNFAIR_LOCK || HAVE_FUTEX
     dispatch_lock *lock = &dgo->dgo_gate.dgl_lock;
    #endif
     uint32_t timeout = 1;
    
     for (;;) {
      os_atomic_rmw_loop(&dgo->dgo_once, old_v, new_v, relaxed, {
       if (likely(old_v == DLOCK_ONCE_DONE)) {
        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
       new_v = old_v | (uintptr_t)DLOCK_WAITERS_BIT;
       if (new_v == old_v) os_atomic_rmw_loop_give_up(break);
      });
      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, 0, timeout++);
    #endif
      (void)timeout;
     }
    }
    
    

总结

     dispatch_once_t onceToken 是静态变量,具有唯一性,在底层被封装成了 dispatch_once_gate_t 类型的变量 l,l 主要是用来获取底层原子封装性的关联,即变量 v,通过 v 来查询任务的状态,如果此时 v 等于DLOCK_ONCE_DONE,说明任务已经处理过一次了,执行时通过锁保证线程安全。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容