在上篇文章函數(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后會喚醒其他所有等待的線程逾柿。