GCD原理(下)

在上篇文章函數(shù)與隊列和gcd原理分析(上)中我們分析了gcd原理,dispatch_async函數(shù) 下面繼續(xù)講解

上篇分析了_dispatch_continuation_init進行了包裝 咱們再來看看_dispatch_continuation_async

我們知道了上一步對信息進行函數(shù)式封裝花竞,那么對于一個異步執(zhí)行來說饱岸,最重要的就是何時創(chuàng)建線程和函數(shù)執(zhí)行呢,那么就再這個方法里面了。

_dispatch_continuation_async

這個方法主要就是執(zhí)行了dx_push方法斋泄,查看其代碼,發(fā)現(xiàn)為宏定義扭吁,主要執(zhí)行了dq_push方法.

那么dq_push又是怎么賦值的呢,由于其是一個屬性盲镶,所以我們可以搜索.dq_pus來查看其賦值侥袜。我們發(fā)現(xiàn)其賦值的地方非常多,但是大體的意思我們可以理解溉贿,就是主要在根隊列枫吧,自定義隊列,主隊列等等進行push操作的時候調(diào)用宇色。

我們知道線程的創(chuàng)建一般都是在根隊列上進行創(chuàng)建的九杂,所以我們直接找根隊列的dq_push賦值,這樣比較快速宣蠕,當然其他的也可以例隆,因為遞歸的關(guān)系最終都會走到這里。

我們發(fā)現(xiàn)_dispatch_root_queue_push方法最終會調(diào)用_dispatch_root_queue_push_inline方法抢蚀,而_dispatch_root_queue_push_inline方法最終又會調(diào)用_dispatch_root_queue_poke镀层。

_dispatch_root_queue_poke這個函數(shù)主要進行了一些容錯的判斷,最終走到了_dispatch_root_queue_poke_slow相關(guān)的方法里

?_dispatch_root_queue_poke_slow

這個方法就是異步執(zhí)行的主要方法皿曲,創(chuàng)建線程也是在此唱逢,由于代碼比較長吴侦,我們還是尋找代碼中的關(guān)鍵節(jié)點來講。


到了這里可以清楚的看到對于全局隊列使用?_pthread_workqueue_addthreads?開辟線程坞古,對于其他隊列使用?pthread_create?開辟新的線程备韧。那么是如何調(diào)用執(zhí)行的呢?其實?_dispatch_root_queues_init?中會首先執(zhí)行第一個任務(wù):

?dispatch_once_f我們后面分析單例會提到 這里看一下 _dispatch_root_queues_init_once

先看下我們的堆棧



調(diào)用_dispatch_worker_thread2 之后就按照堆棧順序執(zhí)行 最終進行了回調(diào)

單例 dispatch_once

通過dispatch_once函數(shù)查看其底層調(diào)用绸贡,可以發(fā)現(xiàn)其最終調(diào)用到dispatch_once_f方法中盯蝴。相關(guān)的代碼如下毅哗。

首先我們知道val一開始為NULL,并將其轉(zhuǎn)換為dispatch_once_gate_t

通過查看_dispatch_once_gate_tryenter源碼听怕,我們知道其在OS底層通過判斷l(xiāng)->dgo_once是否為DLOCK_ONCE_UNLOCKED狀態(tài)

如果成立,則會執(zhí)行_dispatch_once_callout函數(shù)虑绵。執(zhí)行對應(yīng)的block,然后將l->dgo_once置為DLOCK_ONCE_DONE尿瞭,從而保證了只執(zhí)行一次

// 如果 os_atomic_load 為 DLOCK_ONCE_DONE 則直接返回,否則進入

_dispatch_once_gate_tryenter翅睛,在這里首先判斷對象是否存儲過声搁,如果存儲過則則標記為 unlock

回調(diào)


// 在 _dispatch_once_gate_broadcast 中由于執(zhí)行完畢,使用_dispatch_once_mark_done 標記為 done

柵欄函數(shù)

dispatch_barrier_async(柵欄函數(shù))他捕发,通過其命名我們就知道是攔截的意思疏旨。也就是在柵欄函數(shù)之前的任務(wù)執(zhí)行完成后,才能執(zhí)行后邊的任務(wù)扎酷。

dispatch_barrier_sync?需要等待柵欄執(zhí)行完才會執(zhí)行柵欄后面的任務(wù),而dispatch_barrier_async?無需等待柵欄執(zhí)行完,會繼續(xù)往下走(保留在隊列里)


再改成同步柵欄

結(jié)論?dispatch_barrier_sync?需要等待柵欄執(zhí)行完才會執(zhí)行柵欄后面的任務(wù),而dispatch_barrier_async?無需等待柵欄執(zhí)行完,會繼續(xù)往下走(保留在隊列里)

同步執(zhí)行dispatch_sync

死鎖原因

在_dispatch_sync_f_inline中發(fā)現(xiàn)了一個判斷l(xiāng)ikely(dq->dq_width == 1,通過之前隊列的原理我們可以知道檐涝,串行隊列的width是為1的,所以串行的執(zhí)行方法法挨,是在_dispatch_barrier_sync_f中的谁榜。

而且根據(jù)函數(shù)名,我們可以知道_dispatch_barrier是之前講的柵欄函數(shù)的調(diào)用凡纳,所以說柵欄函數(shù)也會走到此方法中窃植。

最終,我們來到了_dispatch_barrier_sync_f_inline函數(shù)中荐糜。

首先執(zhí)行了_dispatch_tid_self方法巷怜。通過源碼跟蹤,我們可以發(fā)現(xiàn)其為宏定義的方法暴氏,底層主要執(zhí)行了_dispatch_thread_getspecific丛版。這個函數(shù)書主要是通過KeyValue的方式來獲取線程的一些信息。在這里就是獲取當前線程的tid偏序,即唯一ID页畦。

我們知道,造成死鎖的原因就是串行隊列上任務(wù)的相互等待研儒。那么必然會通過tid來判斷是否滿足條件豫缨,從而找到了_dispatch_queue_try_acquire_barrier_sync函數(shù)

函數(shù)_dispatch_queue_try_acquire_barrier_sync_and_suspend中独令,從該函數(shù)我們可以知道,通過os_atomic_rmw_loop2o函數(shù)回調(diào)好芭,從OS底層獲取到了狀態(tài)信息燃箭,并返回。

那么返回之后舍败,就執(zhí)行了_dispatch_sync_f_slow函數(shù)招狸。

其中通過源碼可以發(fā)現(xiàn),首先是生成了一些任務(wù)的信息邻薯,然后通過_dispatch_trace_item_push來進行壓棧操作裙戏,從而存放在我們的同步隊列中(FIFO),從而實現(xiàn)函數(shù)的執(zhí)行。

那么產(chǎn)生死鎖的主要檢測就再__DISPATCH_WAIT_FOR_QUEUE__這個函數(shù)中了厕诡,通過查看函數(shù)累榜,發(fā)現(xiàn)它會獲取到隊列的狀態(tài),看其是否為等待狀態(tài)灵嫌,然后調(diào)用_dq_state_drain_locked_by中的異或運算壹罚,判斷隊列和線程的等待狀態(tài),如果兩者都在等待寿羞,那么就會返回YES,從而造成死鎖的崩潰猖凛。


死鎖原因總結(jié)

_dispatch_sync首先獲取當前線程的tid

獲取到系統(tǒng)底層返回的status

獲取到隊列的等待狀態(tài)和tid比較,如果相同绪穆,則表示正在死鎖辨泳,從而崩潰

任務(wù)的執(zhí)行


對于同步任務(wù)的block執(zhí)行,我們在繼續(xù)跟進之前的源碼_dispatch_sync源碼中_dispatch_barrier_sync_f_inline函數(shù)霞幅,觀看其函數(shù)實現(xiàn)漠吻,函數(shù)的執(zhí)行主要是在_dispatch_client_callout方法中。

查看_dispatch_client_callout方法司恳,里面果然有函數(shù)的調(diào)用f(ctxt);


至此途乃,同步函數(shù)的block調(diào)用完成

信號量 dispatch_semaphore

1. dispatch_semaphore_create

這個方法就是函數(shù)式保存,轉(zhuǎn)換成了dispatch_semaphore_t對象扔傅。信號量的處理都是基于此對象來進行的耍共。

2.dispatch_semaphore_wait

wait函數(shù)主要進行了3步操作:

調(diào)用os_atomic_dec2o宏。通過對這個宏的查看猎塞,我們發(fā)現(xiàn)其就是一個對dsema進行原子性的-1操作

判斷value是否>= 0试读,如果滿足條件,則不阻塞荠耽,直接執(zhí)行

調(diào)用_dispatch_semaphore_wait_slow钩骇。通過源碼,我們可以發(fā)現(xiàn)其對timeout的參數(shù)進行了分別的處理


_dispatch_semaphore_wait_slow函數(shù)的處理如下:

default:主要調(diào)用了_dispatch_sema4_timedwait方法,這個方法主要是判斷當前的操作是否超過指定的超時時間倘屹。

DISPATCH_TIME_NOW中的while是一定會執(zhí)行的银亲,如果不滿足條件,已經(jīng)在之前的操作跳出了纽匙,不會執(zhí)行到此务蝠。if操作調(diào)用os_atomic_cmpxchgvw2o,會將value進行+1,跳出阻塞烛缔,并返回_DSEMA4_TIMEOUT超時

DISPATCH_TIME_FOREVER中即調(diào)用_dispatch_sema4_wait,表示會一直阻塞馏段,知道等到single加1變?yōu)?為止,跳出阻塞


3.dispatch_semaphore_signal

了解了wait之后践瓷,對signal的理解也很簡單院喜。os_atomic_inc2o宏定義就是對dsema進行原子性+1的操作,如果大于0当窗,則繼續(xù)執(zhí)行够坐。

總結(jié)一下信號的底層原理

信號量在初始化時要指定 value寸宵,隨后內(nèi)部將這個 value 進行函數(shù)式保存崖面。實際操作時會存兩個 value,一個是當前的value梯影,一個是記錄初始 value巫员。信號的 wait 和 signal 是互逆的兩個操作,wait進行減1的操作甲棍,single進行加1的操作简识。初始 value 必須大于等于 0,如果為0或者小于0 并隨后調(diào)用 wait 方法感猛,線程將被阻塞直到別的線程調(diào)用了 signal 方法

調(diào)度組 dispatch_group

其實dispatch_group的相關(guān)函數(shù)的底層原理和信號量的底層原理的思想是一樣的七扰。都是在底層維護了一個value的值,進組和出組操作時陪白,對value的值進行操作颈走,達到0這個臨界值的時候,進行后續(xù)的操作咱士。

1.dispatch_group_create

和信號量類似立由,創(chuàng)建組后,對其進行了函數(shù)式保存dispatch_group_t,并通過os_atomic_store2o宏定義序厉,內(nèi)部維護了一個value的值

2.dispatch_group_enter

通過源碼锐膜,我們可以知道進組操作,主要是先通過os_atomic_sub_orig2o宏定義弛房,對bit進行了原子性減1的操作道盏,然后又通過位運算& DISPATCH_GROUP_VALUE_MASK獲得真正的value

3.dispatch_group_leave

出組的操作即通過os_atomic_add_orig2o的對值進行原子性的加操作,并通過& DISPATCH_GROUP_VALUE_MASK獲取到真實的value值。如果新舊兩個值相等荷逞,則執(zhí)行_dispatch_group_wake操作牺堰,進行后續(xù)的操作。

4.dispatch_group_async

dispatch_group_async函數(shù)就是對enter和leave的封裝颅围。通過代碼可以看出其和異步調(diào)用函數(shù)類似伟葫,都對block進行的封裝保存。然后再內(nèi)部執(zhí)行的時候院促,手工調(diào)用了dispatch_group_enter和dispatch_group_leave方法筏养。

5.dispatch_group_notify

通過源碼,我們可以發(fā)現(xiàn)常拓,通過調(diào)用os_atomic_rmw_loop2o在系統(tǒng)內(nèi)核中獲取到對應(yīng)的狀態(tài)渐溶,最終還是調(diào)用到了_dispatch_group_wake

_dispatch_group_wake這個函數(shù)主要分為兩部分,首先循環(huán)調(diào)用semaphore_signal告知喚醒當初等待 group 的信號量弄抬,因此dispatch_group_wait函數(shù)得以返回茎辐。


總結(jié)

dispatch_sync將任務(wù)block通過push到隊列中,然后按照FIFO去執(zhí)行掂恕。

dispatch_sync造成死鎖的主要原因是堵塞的tid和現(xiàn)在運行的tid為同一個

dispatch_async會把任務(wù)包裝并保存拖陆,之后就會開辟相應(yīng)線程去執(zhí)行已保存的任務(wù)。

semaphore主要在底層維護一個value的值懊亡,使用signal進行+ +1依啰,wait進行-1。如果value的值大于或者等于0店枣,則取消阻塞速警,否則根據(jù)timeout參數(shù)進行超時判斷

dispatch_group底層也是維護了一個value的值,等待group完成實際上就是等待value恢復(fù)初始值鸯两。而notify的作用是將所有注冊的回調(diào)組裝成一個鏈表闷旧,在dispatch_async完成時判斷value是不是恢復(fù)初始值,如果是則調(diào)用dispatch_async異步執(zhí)行所有注冊的回調(diào)钧唐。

dispatch_once通過一個靜態(tài)變量來標記block是否已被執(zhí)行忙灼,同時使用加鎖確保只有一個線程能執(zhí)行,執(zhí)行完block后會喚醒其他所有等待的線程逾柿。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缀棍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子机错,更是在濱河造成了極大的恐慌爬范,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弱匪,死亡現(xiàn)場離奇詭異青瀑,居然都是意外死亡璧亮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進店門斥难,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枝嘶,“玉大人,你說我怎么就攤上這事哑诊∪悍觯” “怎么了?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵镀裤,是天一觀的道長竞阐。 經(jīng)常有香客問我,道長暑劝,這世上最難降的妖魔是什么骆莹? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮担猛,結(jié)果婚禮上幕垦,老公的妹妹穿的比我還像新娘。我一直安慰自己傅联,他們只是感情好先改,可當我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著纺且,像睡著了一般盏道。 火紅的嫁衣襯著肌膚如雪稍浆。 梳的紋絲不亂的頭發(fā)上载碌,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天,我揣著相機與錄音衅枫,去河邊找鬼嫁艇。 笑死,一個胖子當著我的面吹牛弦撩,可吹牛的內(nèi)容都是我干的步咪。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼益楼,長吁一口氣:“原來是場噩夢啊……” “哼猾漫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起感凤,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤悯周,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后陪竿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體禽翼,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了闰挡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锐墙。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖长酗,靈堂內(nèi)的尸體忽然破棺而出溪北,到底是詐尸還是另有隱情,我是刑警寧澤夺脾,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布刻盐,位于F島的核電站,受9級特大地震影響劳翰,放射性物質(zhì)發(fā)生泄漏敦锌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一佳簸、第九天 我趴在偏房一處隱蔽的房頂上張望乙墙。 院中可真熱鬧,春花似錦生均、人聲如沸听想。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汉买。三九已至,卻和暖如春佩脊,著一層夾襖步出監(jiān)牢的瞬間蛙粘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工威彰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留出牧,地道東北人。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓歇盼,卻偏偏與公主長得像舔痕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子豹缀,可洞房花燭夜當晚...
    茶點故事閱讀 45,870評論 2 361