最近在浏覽react native代碼的時候發現有提到main queue和main thread的差別,很早就有閱讀gcd源碼的沖動,這回總算找到機會了。
閱讀源碼之前先給個結論:main thread 和 main queue是兩個不同的東西。
main queue is bound to main thread.
main thread is not bound to main queue.

just remember this png!!
可能大家對<code>__builtin_expect</code>比較熟悉,這是編譯器可以用來優化執行速度的函數。
程式員在寫if條件的時候,可能知道比較的值更可能是哪種情況,是以就可以使用<code>fastpath</code>
或者<code>slowpath</code>來告訴編譯器,讓編譯來幫忙優化。
<code>fastpath</code>表示條件更可能成立
<code>slowpath</code>表示條件更不可能成立
是以簡單的來說,當我們遇到這個東西的時候,直接忽略,并不會影響我們對代碼的了解。
gcd支援function和block的執行,相應的其提供了兩種方法來支援function和block的入隊,簡單的舉個例子。是以當我們看到不帶f和帶f的同名函數,預設他們幹的是同一回事。
block的執行底層調用的是function的執行。
libdispatch為了在保證性能的情況下,盡量增加代碼的可讀性,大量的使用了宏。
是以了解結構體之前,我們需要先知道幾個宏定義,友善後續分析代碼.
因為本文隻關心queue的實作,是以暫時省略了其他結構體,有興趣的童鞋可以直接下源碼看。
系統基類
該結構體可以看成gcd的基類。
通過上面的結構體定義可以發現,<code>dispatch_object_t</code>可以是union結構體中任何一種類型。
該結構體主要用來封裝block和function
隊列結構體,可能是gcd最重要的結構體了。
隊列屬性結構體。
gcd提供了非常多的功能來簡化針對多核裝置的代碼編寫,這裡我們慢慢添加對常用api的源碼剖析。
當我們擷取global queue來使用的時候,其實質上通過<code>_dispatch_get_root_queue</code>來擷取的非overcommit的預先生成的隊列的。
<code>_dispatch_get_root_queue</code>是從結構體<code>_dispatch_root_queues</code>中擷取相應的優先級的隊列。<code>_dispatch_root_queues</code>區分是否overcommit,定義了4中優先級的隊列,他們分别是(參考前文的圖)。最後1bit是1的代表overcommit。overcommit用來控制線程數能不能超越實體核心數,顯然通過該接口獲得的隊列不會給系統建立過多的隊列。
最後提及一下,<code>_dispatch_root_queues</code>對應的thread實作在<code>_dispatch_root_queue_contexts</code>中,每一個context都是一個線程池,每個線程池的最大線程數限制是255。
可以發現當我們通過<code>dispatch_get_current_queue</code>來擷取目前運作的隊列的時候,我們是通過tsd(thread specific data)來确定到底目前是運作在那個queue上的,每當我們切換queue的時候,都是通過<code>_dispatch_thread_setspecific</code>來設定目前queue。
這裡主要涉及到tsd,也有叫tld的,是個比較有意思的技術。
當我們調用<code>dispatch_queue_create</code>進行queue的建立的時候,其會首先調用<code>_dispatch_queue_init</code>初始化一個queue,該queue預設是default的優先級(還記得前文的圖麼?),并且dq_width是1,也就是串行隊列,并且序列号加1。
如果是并發隊列的話,會将dq_width改成uint32_max,并且将目标queue設定成非overcommit的。overcommit如果被設定成true,那就意味着可以建立超過實體核數目的線程數。是以可以發現,自定義并發隊列線程數目是不會超過實體核心數的,而串行隊列一般是沒有這個限制的。
前面說到序列号加1了,那序列号是幹什麼的呢?在源碼中有這樣一段注釋。
可以發現,序列号是1的時候表示main queue,2的時候表示manager queue,3沒有使用,4到11表示global queue,再往後就是使用者自定義queue了。
那接下來我們看看序列1和序列2的queue是怎麼定義的。
值得說明的是,main queue的目标queue也隻是一個優先級為default的overcommit queue,其背後也是普通的線程池,nothing special。
該隊列是用來管理gcd内部的任務的,比如對于各類source的管理等。
無論走哪個分支,深入檢視之後,可以發現,其最終走的都是<code>dispatch_sync_f</code>,通過<code>_dispatch_block_copy</code>或者<code>block_basic</code>來實作block到function的轉換。
如果dq_width是1的話,也就是dq是串行隊列的話,必須要等待前面的任務執行完成之後才能執行該任務,是以會調用<code>dispatch_barrier_sync_f</code>。barrier的實作是依靠信号量機制來保證的。
如果目前隊列中有對象或者目前隊列處于暫停狀态或者目前隊列沒有運作任何任務的時候,就走<code>_dispatch_barrier_sync_f_slow</code>慢通道,否則的話就直接調用<code>_dispatch_barrier_sync_f_invoke</code>。可以發現<code>dispatch_sync</code>一般來說都是在目前線程執行的,不進行線程的切換,這一點還是要特别注意的。隻有走<code>slow</code>的時候,才會做線程切換。下面就看下走<code>slow</code>路徑是什麼樣的。
代碼比較長,截取開頭一部分,我們可以發現,其通過信号量來同步任務的執行,需要切換線程。
簡單看下<code>_dispatch_function_invoke</code>,主要關注queue和thread的關系并非一一對應的。queue是基于thread的,我們在使用queue的時候,就不應該再操作thread,防止出現意外的情況。
當我們切換線程的時候,我們會先更改tsd,執行block,然後再将之前的dq設定回去。
這個就比較顯而易見了,直接就把block轉成function,然後掉xxx_f函數了。
看過前面幾個例子之後就比較清晰了,如果dq是串行隊列的話,就走<code>dispatch_barrier_async_f</code>,否則的話就需要建立一個<code>dispatch_continuation</code>對象來存放function,然後塞到隊列後面。其實這兩個路徑都是幹了一件事情,就是建立dc對象,然後塞到隊列後面,唯一的差別是
其實到這裡我們可以知道,gcd隊列的阻塞等待就是通過<code>dispatch_obj_barrier_bit</code>這個bit辨別來實作的,全部的辨別有如下幾種:
了解這個對于閱讀源碼作用還是比較大的。
接下去是比較重要的部分,也是隐藏的比較深的部分,我們現在的确把dc給放進隊列裡面了,那什麼時候改dc才會被執行呢?正常的實作應該是上一個任務完成之後主動觸發下一個任務。我們看下幾個函數
可以發現無論是同步的任務執行,還是異步的任務執行,其最終都會調用<code>_dispatch_wakeup</code>,該函數的作用就是觸發下一個任務。
一開始看半天沒看出來咋整的,有個非常不起眼的宏<code>dx_probe</code>,他的定義是<code>(x)->do_vtable->do_probe(x)</code>,搜了一下<code>do_probe</code>,可以發現,在init.c中有相關的定義,我們讨論到現在的基本都是root_queue,是以隻看root_queue。
<code>_dispatch_queue_wakeup_global_slow</code>的代碼這裡就不貼了,在這個函數中會發送生産者消息讓下一個任務執行,同時也會在必要的時候建立線程。這裡是比較隐晦的,但原理和我們猜的也差不多。
看這個代碼斷斷續續也看了幾天,終于看出了一點端倪。感謝網友部落格提供的關鍵資訊說明,少走了很多彎路。特此記錄,以備後用。
<a href="https://bestswifter.com/deep-gcd/">深入了解gcd</a>
<a href="http://www.jianshu.com/p/a639e2159aa1">gcd源碼的分析</a>
<a href="http://libdispatch.macosforge.org/trac/wiki/tutorial">libdispatch</a>
<a href="http://blog.krzyzanowskim.com/2016/06/03/queues-are-not-bound-to-any-specific-thread/">queues are not bound to any specific thread</a>