OC底層原理探索—GCD(下)—— 柵欄函數(shù)褒翰、調(diào)度組欲间、信號(hào)量

柵欄函數(shù)

關(guān)于柵欄函數(shù)楚里,系統(tǒng)提供了兩個(gè)方法

  • dispatch_barrier_async
  • dispatch_barrier_sync
    dispatch_barrier_syncdispatch_barrier_async區(qū)別會(huì)不會(huì)阻塞當(dāng)前的線程,要注意猎贴,柵欄函數(shù)只能控制同一隊(duì)列班缎。
    全局并發(fā)隊(duì)列 :dispatch_get_global_queue會(huì)使柵欄函數(shù)失效

柵欄函數(shù)使用

同步柵欄函數(shù)
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);

    /* 1.異步函數(shù) */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"1");
    });
    
    dispatch_async(concurrentQueue, ^{
        
        NSLog(@"2");
    });
    /* 2. 柵欄函數(shù) */ 
    dispatch_barrier_sync(concurrentQueue, ^{
        NSLog(@"3");
    });
    /* 3. 異步函數(shù) */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"4");
    });
    // 4
    NSLog(@"5");

image.png

這里打印的結(jié)果為12354,接下來分析下

  • 因?yàn)檫@里是dispatch_barrier_sync同步柵欄函數(shù)她渴,阻塞當(dāng)前的線程达址,所以5一定是在3后面打印
  • 柵欄函數(shù)是在同一隊(duì)列的任務(wù),柵欄上方的任務(wù)先執(zhí)行趁耗,當(dāng)上方任務(wù)執(zhí)行完畢再執(zhí)行柵欄內(nèi)部任務(wù)沉唠,最后執(zhí)行柵欄下方任務(wù)
  • 所以1,2先打印,1,2的順序不固定对粪。接下來一定是打印3
  • 后面打印5右冻,是因?yàn)?code>dispatch_async本身存在耗時(shí)操作,4一定在5后面
異步柵欄函數(shù)
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    /* 1.異步函數(shù) */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"1");
    });
    
    dispatch_async(concurrentQueue, ^{
        
        NSLog(@"2");
    });
    /* 2. 柵欄函數(shù) */ // 
    dispatch_barrier_async(concurrentQueue, ^{
        NSLog(@"3");
    });
    /* 3. 異步函數(shù) */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"4");
    });
    // 4
    NSLog(@"5");

image.png

這里的打印順序?yàn)?code>51234著拭,接下來分析下

  • 觀察打印順序和同步柵欄函數(shù)唯一的區(qū)別是5的打印
  • 是因?yàn)楫惒綎艡诤瘮?shù)不會(huì)阻塞當(dāng)前線程纱扭,而dispatch_async存在耗時(shí),所以5先打印儡遮,剩下的順序與同步柵欄函數(shù)一致
柵欄函數(shù)底層分析

通過libdispatch源碼進(jìn)入函數(shù)dispatch_barrier_sync->_dispatch_barrier_sync_f->_dispatch_barrier_sync_f_inline
進(jìn)入_dispatch_barrier_sync_f_inline

image.png

  • _dispatch_sync_f_slow
  • _dispatch_sync_recurse
  • _dispatch_lane_barrier_sync_invoke_and_complete
    這里有這3個(gè)方法我們不知道最終進(jìn)入哪個(gè)乳蛾,我們通過符號(hào)斷點(diǎn)來確認(rèn)
    image.png

    進(jìn)入_dispatch_sync_f_slow
    image.png

    繼續(xù)跟蹤流程,并添加_dispatch_sync_invoke_and_complete_recurse的符號(hào)斷點(diǎn)鄙币,并進(jìn)入
    image.png

    進(jìn)入_dispatch_sync_complete_recurse
    image.png

    這里我們發(fā)現(xiàn)是個(gè)do while循環(huán)肃叶,這里思考下為什么這么寫?
  • 柵欄函數(shù)起到的的是同步作用十嘿,同一隊(duì)列中因惭,柵欄前的任務(wù)沒有執(zhí)行柵欄函數(shù)是不會(huì)走的
  • 所以這里需要遞歸處理柵欄函數(shù)前面的任務(wù)
  • _dispatch_sync_complete_recurse中的遞歸中,先判斷barrier是否存在绩衷,如果存在則需要先把柵欄前的任務(wù)dx_wakeup 全部喚醒蹦魔。喚醒成功后才會(huì)執(zhí)行_dispatch_lane_non_barrier_complete

先來查看dx_wakeup,來查看barrier什么時(shí)候被移出咳燕,dx_wakeup是通過宏定義的函數(shù)

#define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)

搜索dq_wakeup

image.png

由上圖可知串行隊(duì)列和并行隊(duì)列都走了_dispatch_lane_wakeup勿决,而全局并發(fā)隊(duì)列走了_dispatch_root_queue_wakeup

  • 串行、并行隊(duì)列招盲,進(jìn)入_dispatch_lane_wakeup
    image.png

    進(jìn)入_dispatch_lane_barrier_complete
    image.png
  • 如果是串行隊(duì)列則會(huì)進(jìn)行等待低缩,知道其他任務(wù)執(zhí)行完成,按順序執(zhí)行
  • 如果是并行隊(duì)列,調(diào)用_dispatch_lane_drain_non_barriers將柵欄前的任務(wù)按照異步的放心執(zhí)行
  • 全局并發(fā)隊(duì)列
    當(dāng)是全局并發(fā)隊(duì)列的時(shí)候,進(jìn)入_dispatch_root_queue_wakeup
    image.png

    由上圖可知曹货,全局并發(fā)隊(duì)列并沒有柵欄函數(shù)的相關(guān)處理流程咆繁,這也是柵欄函數(shù)在全局并發(fā)隊(duì)列失效的原因

【問題】為什么全局并發(fā)隊(duì)列中不對(duì)柵欄函數(shù)進(jìn)行處理
【答】因?yàn)槿植l(fā)隊(duì)列除了被我們使用讳推,系統(tǒng)也在使用,如果添加了柵欄函數(shù)么介,會(huì)導(dǎo)致隊(duì)列運(yùn)行的阻塞娜遵,從而影響系統(tǒng)級(jí)的運(yùn)行,所以柵欄函數(shù)也就不適用于全局并發(fā)隊(duì)列壤短。

信號(hào)量(dispatch_semaphore_t)

  • dispatch_semaphore_create創(chuàng)建一個(gè)Semaphore设拟,并初始信號(hào)總量
  • dispatch_semaphore_wait信號(hào)量減1,當(dāng)信號(hào)量小于0時(shí),就會(huì)所在線程發(fā)生阻塞久脯,大于等于0時(shí)纳胧,正常執(zhí)行
  • dispatch_semaphore_signal信號(hào)量加1
案例

image.png

這里我i們的正常理解應(yīng)該是先執(zhí)行任務(wù)1,但是這里初始化的信號(hào)總量為0,且在任務(wù)1中dispatch_semaphore_wait起到了加鎖作用,所以先去執(zhí)行任務(wù)2,且發(fā)出了dispatch_semaphore_signal解鎖信號(hào)帘撰,再去執(zhí)行任務(wù)`

信號(hào)量底層分析

dispatch_semaphore_wait

image.png

首先對(duì)信號(hào)量做減1操作跑慕,當(dāng)信號(hào)量大于等于0時(shí)直接返回,否則進(jìn)入_dispatch_semaphore_wait_slow方法
image.png

對(duì)timeout進(jìn)行判斷

  • 如果是默認(rèn)的摧找,會(huì)直接跳出
  • DISPATCH_TIME_NOW核行,會(huì)進(jìn)行一個(gè)超時(shí)處理
  • DISPATCH_TIME_FOREVER會(huì)進(jìn)入_dispatch_sema4_wait方法
    進(jìn)入_dispatch_sema4_wait方法
void
_dispatch_sema4_wait(_dispatch_sema4_t *sema)
{
    int ret = 0;
    do {
        ret = sem_wait(sema);
    } while (ret == -1 && errno == EINTR);
    DISPATCH_SEMAPHORE_VERIFY_RET(ret);
}

我們發(fā)現(xiàn)有個(gè)do while方法,并調(diào)用sem_wait蹬耘,全局搜索sem_wait

image.png

并沒有搜索出sem_wait方法的實(shí)現(xiàn)
所以_dispatch_sema4_waitdo while就是個(gè)死循環(huán)芝雪,原因就是要讓該任務(wù)一致處于等待狀態(tài)

dispatch_semaphore_signal

image.png

通過os_atomic_inc2o對(duì)信號(hào)量做+1操作,如果大于0直接返回综苔。
如果加過一次后仍小于0,則會(huì)拋出異常Unbalanced call to dispatch_semaphore_signal()并調(diào)用_dispatch_semaphore_signal_slow方法惩系,見下圖:
image.png

這里會(huì)開啟一個(gè)循環(huán),對(duì)信號(hào)量加一操作如筛,知道滿足條件位置

【總結(jié)】信號(hào)來那個(gè)在實(shí)際開發(fā)中的作用

  • 保持線程同步堡牡,將異步執(zhí)行的任務(wù),轉(zhuǎn)換成同步操作
  • 保證線程安全杨刨,為線程加鎖(自旋鎖)

調(diào)度組

dispatch_group_create創(chuàng)建組
dispatch_group_async進(jìn)組任務(wù)
dispatch_group_notify進(jìn)組任務(wù)執(zhí)行完畢通知
dispatch_group_wait 進(jìn)組任務(wù)執(zhí)行等待時(shí)間

dispatch_group_enter 進(jìn)組
dispatch_group_leave 出組
兩者搭配使用

案例實(shí)現(xiàn)1

image.png

我們?cè)诋惒骄€程中加了個(gè)sleep(2),這個(gè)時(shí)候在主線程打印為空數(shù)組,但是在我們的dispatch_group_notify中是能夠打印出數(shù)組的內(nèi)容晤柄。是因?yàn)樵谙嗤M中的任務(wù),都執(zhí)行完畢后會(huì)走dispatch_group_notify該方法妖胀。
【注意】dispatch_group_enterdispatch_group_leave要成對(duì)出現(xiàn)可免,并且dispatch_group_enter在執(zhí)行任務(wù)前,dispatch_group_leave任務(wù)執(zhí)行完成后調(diào)用做粤,否則順序錯(cuò)誤會(huì)報(bào)錯(cuò)

案例實(shí)現(xiàn)2

image.png

這里直接將任務(wù)放在dispatch_group_async,最終結(jié)果和上述案例相同捉撮,其實(shí)dispatch_group_async就是底層封裝了dispatch_group_enter 和dispatch_group_leave

調(diào)度組底層分析

dispatch_group_create

image.png

調(diào)用_dispatch_group_create_with_count并將信號(hào)量默認(rèn)傳0
image.png

通過os_atomic_store2o進(jìn)行保存

dispatch_group_enter

image.png

默認(rèn)信號(hào)量為0 怕品,所以信號(hào)量減1,由0-1,old_bits等于-1

dispatch_group_leave

image.png

信號(hào)量加1,此時(shí)的newState等于0巾遭,oldState等于-1

#define DISPATCH_GROUP_VALUE_MASK       0x00000000fffffffcULL
#define DISPATCH_GROUP_VALUE_1          DISPATCH_GROUP_VALUE_MASK

old_state & DISPATCH_GROUP_VALUE_MASK等于0肉康,即old_value等于0
也就是 old_value 與DISPATCH_GROUP_VALUE_1不會(huì)相等闯估,最終調(diào)用if中的 return _dispatch_group_wake_dispatch_group_wake也就是去喚醒dispatch_group_notify

dispatch_group_notify

image.png

這里判斷old_state == 0就去喚醒函數(shù)的執(zhí)行流程吼和,在上一步已經(jīng)分析出old_state = 0

所以這里也就解釋了dispatch_group_enter和dispatch_group_leave為什么要配合起來使用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末涨薪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子炫乓,更是在濱河造成了極大的恐慌刚夺,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件末捣,死亡現(xiàn)場(chǎng)離奇詭異侠姑,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)箩做,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門莽红,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人邦邦,你說我怎么就攤上這事安吁。” “怎么了燃辖?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵鬼店,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我郭赐,道長(zhǎng)薪韩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任捌锭,我火速辦了婚禮俘陷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘观谦。我一直安慰自己拉盾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布豁状。 她就那樣靜靜地躺著捉偏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泻红。 梳的紋絲不亂的頭發(fā)上夭禽,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音谊路,去河邊找鬼讹躯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的潮梯。 我是一名探鬼主播骗灶,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼秉馏!你這毒婦竟也來了耙旦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤萝究,失蹤者是張志新(化名)和其女友劉穎免都,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體糊肤,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡琴昆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了馆揉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片业舍。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖升酣,靈堂內(nèi)的尸體忽然破棺而出舷暮,到底是詐尸還是另有隱情,我是刑警寧澤噩茄,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布下面,位于F島的核電站,受9級(jí)特大地震影響绩聘,放射性物質(zhì)發(fā)生泄漏沥割。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一凿菩、第九天 我趴在偏房一處隱蔽的房頂上張望机杜。 院中可真熱鬧,春花似錦衅谷、人聲如沸椒拗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚀苛。三九已至,卻和暖如春玷氏,著一層夾襖步出監(jiān)牢的瞬間堵未,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來泰國打工盏触, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留渗蟹,地道東北人侦厚。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像拙徽,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诗宣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容