libdispatch 結構型別

林欣達發表於2017-06-11

概述

GCD是一套強大的多執行緒方案,提供了多種任務佇列來提高開發效率,通過閱讀libdispatch的原始碼可以更好的理解GCD的工作流程,幫助我們設計更好的程式碼
libdispatch 結構型別

結構型別

libdispatch使用巨集定義實現了大量的模板結構型別,除此之外還使用了unionenum結合的方式實現動態引數型別的靈活性:

  • queue_type:佇列型別,例如全域性佇列
  • source_type:資源統稱,queue或者function都可以看做是一個資源
  • semaphore_type:訊號型別,訊號可以保證資源同時多執行緒競爭下的安全
  • continuation_type:派發任務會被封裝成dispatch_continuation_t,然後被壓入佇列中

對於libdispatch的結構體型別來說,都存在DISPATCH_STRUCT_HEADER(x)型別的變數。通過##拼接變數名的方式對不同的型別生成動態的引數變數


聯合體union保證了各變數享用同一個記憶體地址,這也意味著各變數是互斥的。通過聯合體結構,libdispatch實現了型別強制轉換的效果。另外,通過巨集定義DISPATCH_DECL(name)來保證所有dispatch_xxx_t變數實際上是一個指向dispatch_xxx_s的指標:

一方面,union在使用時會分配一塊足夠大的記憶體(能夠容納任一一種型別),這意味著可以隨時更換儲存資料,同樣可能造成資料的破壞;另一方面,它讓C函式擁有了返回引數多樣化的靈活性。但是要記住不同的資料型別在生成自身的do_vtable也有不同的表現:


queue為例,其結構型別為dispatch_queue_s,鑑於原始碼中使用了大量的巨集定義增加屬性,下面的結構是替換一部分巨集定義後的結構:

一個執行緒任務佇列有這麼幾個重要的屬性:

  • do_vtable
    虛擬表,採用類似於C++的模板方式定義了一個結構體vtable,主要儲存了結構型別相關的描述資訊
  • do_targetq
    目標佇列,GCD允許我們將某個任務佇列指派到另外的任務佇列中執行。當do_targetq不為空時,async的實現有會所不同
  • dq_width
    最大併發數,序列/主執行緒的這個值是1
  • do_ctxt
    執行緒上下文,用來儲存執行緒池相關資料,比如用於執行緒掛起和喚醒的訊號量、執行緒池尺寸等
  • do_suspend_cnt
    用作暫停標記,當大於等於2時表示任務為延時任務。在任務達到時會修改標記,然後喚醒佇列排程任務

派發任務會被包裝成dispatch_continuation_s結構體物件,同樣

一個dispatch_continuation_s變數不總是隻包裝了單個任務,它被設定成複用機制,通過TSD的方式保證每個執行緒可以擁有一定數量的複用continuation,以此來減少不必要的記憶體分配開銷。

  • dc_func
    承擔執行任務的物件,巨集定義_dispatch_client_callout最終會以dc_func(dc_ctxt)的方式回撥
  • dc_ctxt
    儲存了continuation物件的上下文資料,同樣用於執行任務
  • do_vtable
    只有當do_vtable的值小於127時才表示變數是一個continuation,派發到主/序列佇列的任務會被標記DISPATCH_OBJ_BARRIER_BIT屏障標記

libdispatch的結構物件都擁有自己的一張do_vtable虛擬表,同樣採用模板式的方式生成,每一個具體的結構型別會生成一張對應型別的虛擬表,但是屬性基本是統一的

虛擬表採用型別名拼接的方式生成不同型別的過載函式,由於libdispatch型別採用union結構,這兩者結合極大的保證了執行的靈活性

  • do_type
    資料的具體型別,詳見上文中的列舉值
  • do_kind
    資料的型別描述字串,比如全域性佇列為global-queue
  • do_debug
    debug方法,用來獲取除錯時需要的變數資訊字串
  • do_probe
    用於檢測傳入物件中的一些值是否滿足條件
  • do_invoke
    喚醒佇列的方法,全域性佇列和主佇列此項為NULL

disaptch_root_queue_context_s儲存了執行緒執行過程中的上下文資料,預設情況下已經建立了多個全域性的上下文物件

上下文物件在執行任務的過程中儲存了執行緒訊號資訊以及可用執行緒池數量。

  • dgq_pending_dgq_pad
    當前版本原始碼中未使用
  • dgq_thread_mediator
    用於判斷是否存在可用執行緒資源,如果存在返回1,否則後續將建立新執行緒執行任務
  • dgq_thread_pool_size
    執行緒池剩餘可用數量,只有dgq_thread_mediator的查詢返回0並且此項大於0時會嘗試建立新執行緒用以執行任務

GCD建立了總共八個四種優先順序的全域性上下文物件,每個上下文最多可容納255個執行緒數量


dispatch_semaphore_s是效能稍次於自旋鎖的的訊號量物件,用來保證資源使用的安全性。

相比其他的結構,訊號量的內部要簡潔的多,主要使用的就三個屬性:

  • dsema_value
    當前訊號值,當這個值小於0時無法訪問加鎖資源
  • dsema_orig
    初始化訊號值,限制了同時訪問資源的執行緒數量
  • dsema_sent_ksignals
    由於mach訊號可能會被意外喚醒,通過原子操作來避免虛假訊號

通過一張圖表示上面提及的四種資料型別的關係:
libdispatch 結構型別

執行緒私有變數

程式中的全域性變數和靜態變數是所有執行緒都能訪問的共享變數,這意味著訪問這樣的資料需要昂貴的同步花銷,Thread-specific-Data執行緒私有資料機制讓每一個執行緒擁有私有的全域性變數。libdispatch提供了四個pthread_key來存取這些資料,在初始化階段進行初始化:

基於大概率使用的派發任務,libdispatch快取了dispatch_continuation_s,採用複用模式的做法在每次async中嘗試去獲取空閒的continuation變數,通過兩個函式存取資料:

相關文章