GCD ③ dispatch_barrier

dispatch_barrier (栅栏)

    在访问数据库或文件时,如前所述,使用 Serial Dispatch Queue 可避免数据竟争的问题。
    写入处理确实不可与其他的写入处理以及包含读取处理的其他某些处理并行执行。但是如果读取处理只是与读取处理并行执行,那么多个并行执行就不会发生问题。也就是说,为了高效率地进行访问,读取处理追加到 Concurrent Dispatch Queue 中,写入处理在任一个读取处理没有执行的状态下,追加到 Serial Dispatch Queue 中即可(在写入处理结束之前,读取处理不可执行)。
    虽然利用 Dispatch Groupdispatch_set_target_queue 函数也可实现,但是源代码会很复杂。 GCD 为我们提供了更为聪明的解决方法 dispateh_barrier_async 函数。该函数同 dispatch_queue_crcate 函数生成的 Concurrent Dispatch Queue 一起使用。

  • dispatch_barrier_async 控制任务执⾏顺序,前⾯的任务执⾏完毕才会来到这⾥
  • dispatch_barrier_sync 作⽤相同,但是这个会堵塞线程,影响后⾯的任务执⾏

栅栏函数只能控制同⼀并发队列

    dispatch_barrier_async 函数会等待追加到 Concurrent Dispatch Queue 上的并行执行的处理全部结束之后,再将指定的处理追加到该 Concurrent Dispatch Queue 中。然后在由 dispatch_barrier_async 函数追加的处理执行完毕后, Concurrent Dispatch Queue 才恢复为一般的动作,追加到该 Concurrent Dispatch Queue 的处理又开始并行执行。如下仅使用 dispatch_barrier_async 函数代替 dispatch_async 函数即可。

dispatch_async(queue, blk0ForReading);
dispatch_async(queue, blk1ForReading);
dispatch_async(queue, blk2ForReading);
dispatch_async(queue, blk0ForReading);
dispatch_barrier_async(queue,blkForWriting);
dispatch_async(queue, blk4ForReading);
dispatch_async(queue, blk5ForReading);
Dispatch_barrier_async.png

dispatch_barrier_sync 实现

    在 libdispatch 中搜索 dispatch_barrier_sync(dispatch_queue_t dq, dispatch_block_t work) 函数,可以看到函数调用栈为 dispatch_barrier_sync -> _dispatch_barrier_sync_f -> _dispatch_barrier_sync_f_inline;

void
dispatch_barrier_sync(dispatch_queue_t dq, dispatch_block_t work)
{
  //...
 _dispatch_barrier_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}
DISPATCH_NOINLINE
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_barrier_sync_f_inline 函数:

DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_barrier_sync_f_inline(dispatch_queue_t dq, void *ctxt,
  dispatch_function_t func, uintptr_t dc_flags)
{
 dispatch_tid tid = _dispatch_tid_self();

 if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
  DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
 }

 dispatch_lane_t dl = upcast(dq)._dl;
 //刚刚获得进入队列状态的屏障锁
 if (unlikely(!_dispatch_queue_try_acquire_barrier_sync(dl, tid))) {
  return _dispatch_sync_f_slow(dl, ctxt, func, DC_FLAG_BARRIER, dl,
    DC_FLAG_BARRIER | dc_flags);
 }
//验证target是否存在,如果存在,加入栅栏函数的递归查找 是否等待
 if (unlikely(dl->do_targetq->do_targetq)) {
  return _dispatch_sync_recurse(dl, ctxt, func,
    DC_FLAG_BARRIER | dc_flags);
 }
 _dispatch_introspection_sync_begin(dl);
 _dispatch_lane_barrier_sync_invoke_and_complete(dl, ctxt, func
   DISPATCH_TRACE_ARG(_dispatch_trace_item_sync_push_pop(
     dq, ctxt, func, dc_flags | DC_FLAG_BARRIER)));
}

_dispatch_barrier_sync_f_inline 执行流程如下:

  • 通过 _dispatch_tid_self 获取线程ID

  • 通过 _dispatch_queue_try_acquire_barrier_sync 判断线程状态,尝试挂起线程

  • 通过 _dispatch_sync_recurse 递归查找栅栏函数的target

  • 通过 _dispatch_lane_barrier_sync_invoke_and_complete 执行 block 并释放

_dispatch_lane_barrier_sync_invoke_and_complete

    继续查看 _dispatch_lane_barrier_sync_invoke_and_complete 函数

DISPATCH_NOINLINE
static void
_dispatch_lane_barrier_sync_invoke_and_complete(dispatch_lane_t dq,
  void *ctxt, dispatch_function_t func DISPATCH_TRACE_ARG(void *dc))
{
 _dispatch_sync_function_invoke_inline(dq, ctxt, func);
 //...

 // similar to _dispatch_queue_drain_try_unlock
 os_atomic_rmw_loop2o(dq, dq_state, old_state, new_state, release, {
  new_state  = old_state - DISPATCH_QUEUE_SERIAL_DRAIN_OWNED;
  new_state &= ~DISPATCH_QUEUE_DRAIN_UNLOCK_MASK;
  new_state &= ~DISPATCH_QUEUE_MAX_QOS_MASK;
  if (unlikely(old_state & fail_unlock_mask)) {
   os_atomic_rmw_loop_give_up({
    return _dispatch_lane_barrier_complete(dq, 0, flags);
   });
  }
 });
 if (_dq_state_is_base_wlh(old_state)) {
  _dispatch_event_loop_assert_not_owned((dispatch_wlh_t)dq);
 }
}

_dispatch_lane_barrier_sync_invoke_and_complete 函数执行流程如下:

  1. _dispatch_sync_function_invoke_inline 同步执行 block,与 dispatch_sync 一致

  2. os_atomic_rmw_loop2o 进行状态释放,调用 dispatch_lane_barrier_complete 执行完成。

dispatch_barrier_async 实现

    查看 dispatch_barrier_async 可以发现与 dispatch_async类似,差别在于 DC_FLAG_BARRIER,根据 DC_FLAG_BARRIER 标识,在执行时等待队列中的任务执行完成。

DISPATCH_NOINLINE
void
dispatch_barrier_async_f(dispatch_queue_t dq, void *ctxt,
  dispatch_function_t func)
{
 dispatch_continuation_t dc = _dispatch_continuation_alloc_cacheonly();
 uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_BARRIER;
 dispatch_qos_t qos;

 if (likely(!dc)) {
  return _dispatch_async_f_slow(dq, ctxt, func, 0, dc_flags);
 }

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

推荐阅读更多精彩内容