1. 程式人生 > >GCD深入學習之GCD的初識

GCD深入學習之GCD的初識

如果移動端訪問不佳,可以訪問我的個人部落格

現在網上關於GCD的介紹已經很多了,在專案中也經常用到,但是沒怎麼深入研究過,打算寫一系列關於GCD使用,參考其他大神寫的部落格和Apple的技術文件總結一下,一是自己深入學習一下,二是以後忘了可以回過頭來溫習一下~

什麼是GCD?

GCD全名是Grand Central Dispatch(大中央排程器),是系統級的,存在於libdispatch.dylib這個庫裡,是Apple開發的一個多核程式設計的解決方法,它提供了一下幾種好處:

  • GCD用純C編寫,可以提高應用程式的響應能力,更加高效;
  • GCD使用簡單,會自動利用更多的CPU核心(比如雙核、四核),自動管理執行緒的生命週期(建立執行緒、排程任務、銷燬執行緒),程式設計師只需要告訴GCD想要執行什麼任務,不需要編寫任何執行緒管理程式碼,提供更容易併發模型,有助於避免併發錯誤。

GCD的相關術語

要了解GCD,你必須熟悉相關的執行緒和併發的幾個概念。這些既可以是模糊的,微妙的,所以花點時間在GCD的背景下簡要回顧一下他們。

序列和併發

在執行任務時,序列是任務順序執行,執行完一個下一個。併發就是任務可能同時執行多個任務。在GCD中一個任務就是一個閉包,這比NSOperation中的任務更加容易理解。

同步和非同步

在計算機領域,同步就是指一個程序在執行某個請求的時候,若該請求需要一段時間才能返回資訊,那麼這個程序將會一直等待下去,直到收到返回資訊才繼續執行下去;非同步是指程序不需要一直等下去,而是繼續執行下面的操作,不管其他程序的狀態。當有訊息返回時系統會通知程序進行處理,這樣可以提高執行的效率。

臨界區

通過對多執行緒的序列化來訪問公共資源或一段程式碼,速度快,適合控制資料訪問。在任意時刻只允許一個執行緒對共享資源進行訪問,如果有多個執行緒試圖訪問公共資 源,那麼在有一個執行緒進入後,其他試圖訪問公共資源的執行緒將被掛起,並一直等到進入臨界區的執行緒離開,臨界區在被釋放後,其他執行緒才可以搶佔。

死鎖

是指兩個或兩個以上的程序在執行過程中,由於競爭資源或者由於彼此通訊而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的程序稱為死鎖程序,簡單來說就是A執行緒在等待B執行緒完成後執行,B執行緒也在等待A執行緒完成後執行,這樣就出現了死鎖的現象。

執行緒安全

執行緒安全的程式碼可以從多個執行緒或併發任務安全地呼叫,而不會造成任何問題(資料損壞,系統崩潰等)。例如當你多執行緒程式設計時,你用let定義一個數組,因為它是隻讀的,你能在同一時間不同執行緒去使用它,而不會造成執行緒安全的問題,然而當你用var定義一個數組時就不一樣了,它不是執行緒安全的,當多個執行緒在同一時間訪問和修改陣列時會產生不可預知的結果。

上下文切換

上下文切換是儲存和恢復執行的狀態,是你在一個程序中執行不同的執行緒之間切換的過程。這個過程是編寫多工處理應用程式時很常見,但也帶來了一些額外的開銷成本。就像併發就是通過切換上下文來實現的。

併發和並行

在多核裝置上執行任務時,每個CPU可以單獨工作,每個CPU同時執行不同的任務,這就是並行,然而為了使單核的裝置實現這種效果達到類似的效果,因為它們只有一個執行緒,它們只能通過快速的上下文切換來達到並行的假象,這就是併發,如下圖所示:

併發

GCD佇列

GCD用dispatch queues來處理提交的任務,佇列用FIFO(先進先出)的原理來管理這些任務,所有的dispatch queues本身是執行緒安全的,你可以從多個執行緒去訪問它們,在GCD 中提供了兩種佇列,分別是序列佇列和併發佇列。

序列佇列

序列佇列保證同一時間佇列裡只有一個任務在執行,只有等待第一個任務執行完成後才會執行下一個任務,你也不知道兩個任務之間的間隔時間是多少,如下圖所示:

串號佇列

使用序列佇列有一下的優點:

  1. 能確保對一個共享資源進行序列化的訪問,避免了資料競爭;
  2. 任務的執行順序是可預知的,你向一個序列佇列提交任務時,它們被執行的順 序與它們被提交的順序相同;

併發佇列

併發佇列可以讓你並行的執行多個任務。任務按照它們被加入到佇列中的順序依次開始,但是它們都是併發的被執行,並不需要彼此等待才開始。併發佇列能保證任務按同一順序開始,但你不能知道執行的順序、執行的時間以及在某一時刻正在被執行任務的數量,具體如下圖所示:

併發佇列

GCD 的佇列型別

在使用過程中系統會自動給每個應用提供一個序列佇列和四個併發佇列,其中序列佇列為全域性可用的序列佇列,在應用的主執行緒中執行任務且只有一個,這個佇列被用來更新 App 的 UI,執行所有與更新 UIViews 相關的任務。該佇列中同一時刻只執行一個任務,這就是為什麼當你在主佇列中執行一個繁重的任務時UI會被阻塞的原因。GCD提供了一下三種佇列:

  • 主佇列:任務以序列的方式執行在您的應用程式的主執行緒;
  • 併發佇列:任務在先進先出的順序列中移除,但併發執行,可以按照任何順序完成;
  • 序列佇列:以先進先出順序執行一次任務。

除主佇列之外,系統還提供了4個併發佇列。我們管它們叫 Global Dispatch queues(全域性派發佇列)。這些佇列對整個應用來說是全域性可用的,彼此只有優先順序高低的區別。要使用其中一個全域性併發佇列的話,你得使用 dispatch_get_global_queue 函式獲得一個你想要的佇列的引用,該函式的第一個引數取如下值:

  • DISPATCH_QUEUE_PRIORITY_HIGH:高優先順序的佇列,高於其他任務優先順序的佇列。

  • DISPATCH_QUEUE_PRIORITY_DEFAULT:預設優先順序佇列,高於下面兩個優先順序,

  • DISPATCH_QUEUE_PRIORITY_LOW:低優先順序佇列,低於上面兩個優先順序。
  • DISPATCH_QUEUE_PRIORITY_BACKGROUND:最低的優先順序,使用它可以儘可能的減少對系統的影響。

你可以建立任何數量的序列或併發佇列。使用併發佇列的情況下,即使你可以自己建立,但是還是強烈建議你使用上面那四個全域性佇列,避免帶來沒必要的麻煩。

建立和管理GCD佇列

dispatch_get_main_queue() -> dispatch_queue_t!

返回值

通過這個函式來獲取主執行緒佇列:

//返回主執行緒佇列
dispatch_get_main_queue()

dispatch_get_global_queue(identifier: Int, _ flags: UInt) -> dispatch_queue_t!

返回值

通過這個函式來獲取系統提供的4個併發佇列

引數列表

引數名 引數描述
identifier 代表的佇列的優先順序,為DISPATCH_QUEUE_PRIORITY_HIGH、DISPATCH_QUEUE_PRIORITY_DEFAULT、DISPATCH_QUEUE_PRIORITY_LOW、DISPATCH_QUEUE_PRIORITY_BACKGROUND中的一個
flags 蘋果官方解釋是保留已提供將來使用,目前讓這個引數為0
//返回DISPATCH_QUEUE_PRIORITY_HIGH優先順序的佇列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
//返回DISPATCH_QUEUE_PRIORITY_DEFAULT優先順序的佇列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
//返回DISPATCH_QUEUE_PRIORITY_LOW優先順序的佇列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)
//返回DISPATCH_QUEUE_PRIORITY_BACKGROUND優先順序的佇列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)

dispatch_queue_create(label: UnsafePointer, _ attr: dispatch_queue_attr_t!) -> dispatch_queue_t!

返回值

通過這函式來建立一個新的佇列。

引數列表

引數 引數描述
label 為你建立的佇列的標籤,建議用反向DNS的命名風格(com.example.myqueue),這個引數是可選的,可以為NULL。
attr GCD佇列的屬性,這個引數來選擇建立的是序列佇列(DISPATCH_QUEUE_SERIAL)還是併發佇列(DISPATCH_QUEUE_CONCURRENT)和佇列的優先級別。
//建立一個標識為com.wcl.www的併發佇列
dispatch_queue_create("com.wcl.www", DISPATCH_QUEUE_CONCURRENT)
//建立一個標識為com.imwcl.www的序列佇列
dispatch_queue_create("com.imwcl.www", DISPATCH_QUEUE_SERIAL)

dispatch_get_current_queue()

返回值

返回當前任務中的佇列。

dispatch_queue_attr_make_with_qos_class(attr: dispatch_queue_attr_t!, _ qos_class: dispatch_qos_class_t, _ relative_priority: Int32) -> dispatch_queue_attr_t!

返回值

返回一個適用於建立一個想要的服務質量資訊的GCD佇列的屬性。主要用於dispatch_queue_create函式。

引數列表

引數 引數描述
attr GCD佇列的屬性,這個引數來選擇建立的是序列佇列(DISPATCH_QUEUE_SERIAL)還是併發佇列(DISPATCH_QUEUE_CONCURRENT)和佇列的優先級別。
qos_class 這個引數為佇列優先順序,同樣為DISPATCH_QUEUE_PRIORITY_HIGH、DISPATCH_QUEUE_PRIORITY_DEFAULT、DISPATCH_QUEUE_PRIORITY_LOW、DISPATCH_QUEUE_PRIORITY_BACKGROUND中的一個。
relative_priority 這個引數為QOS類中相對優先順序,這個值必須小於0,大於QOS_MIN_RELATIVE_PRIORITY,根據log資料發現QOS_MIN_RELATIVE_PRIORITY的值為-15,那說明第二個引數的值在0~-15之間。

第三個引數為QOS類中相對優先順序,也就是第二個引數的類,這個值必須小於0,大於QOS_MIN_RELATIVE_PRIORITY

global queue優先順序對映到以下quality-of-service類:

  • DISPATCH_QUEUE_PRIORITY_HIG對映到QOS_CLASS_USER_INITIATED
  • DISPATCH_QUEUE_PRIORITY_DEFAULT對映到QOS_CLASS_DEFAULT
  • DISPATCH_QUEUE_PRIORITY_LOW對映到QOS_CLASS_UTILITY
  • DISPATCH_QUEUE_PRIORITY_BACKGROUND對映到QOS_CLASS_BACKGROUND
let att = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, QOS_MIN_RELATIVE_PRIORITY)
//根據att來建立一個佇列
dispatch_queue_create("com.wcl.www", att)

dispatch_queue_get_label(dispatch_queue_t queue)

返回值

返回已經建立佇列的指定標籤。如果佇列在建立過程中沒有提供標籤,則可能返回NULL。

//返回佇列的標籤
dispatch_queue_get_label(dispatch_get_main_queue())

dispatch_main( void)

執行主佇列上被提交的所有block。這個函式是為主執行緒而存在的並且等待執行提交到主佇列中的block。在主執行緒中呼叫了UIApplicationMain(iOS) ,NSApplicationMain(OS X),或者CFrunLoopRun的應用程式一定不要呼叫dispatch_main。

int main(int argc, const charchar * argv[])  
{  

    @autoreleasepool {  
        dispatch_async(dispatch_get_main_queue(), ^{  
            NSLog(@"等待1。。。。");  
        });  
        dispatch_async(dispatch_get_main_queue(), ^{  
            NSLog(@"等待1。。。。");  
        });  
        dispatch_main();  
    }  
    return 0;  
} 

如上:如果不呼叫dispatch_main()函式,則不會打印出結果。

dispatch_set_target_queue(object: dispatch_object_t!, _ queue: dispatch_queue_t!)

引數列表

引數 引數描述
object 要修改的物件,該引數不能為空。
queue 處理這個物件的目標佇列,這個引數不能為NULL

給GCD物件設定目標佇列,這個目標佇列負責處理這個物件。處理的物件分別有一下幾種:

  • GCD佇列:dispatch_queue_t,一個GCD佇列的優先順序是繼承自它的目標佇列的。使用dispatch_get_global_queue函式去獲得一個合適的目標佇列,這個目標佇列就是你所需的優先順序。

    如果你提交一個block到一個序列佇列中,並且這個序列佇列的目標佇列是一個不同的序列佇列,那麼這個block將不會與其他被提交到這個目標佇列的block或者任何其他有相同目標佇列的佇列同時呼叫。

  • GCD資料來源:dispatch_source_t,為一個GCD資料來源的目標佇列指定了它的事件處理者的block和取消事件處理的block。

  • GCD I/O通道:dispatch_io_t,一個GCD I/O通道的目標佇列指定了被執行的I/O操作。這可能會影響I/O操作結果的優先順序。例如,如果這個通道的目標佇列的優先順序被設定為DISPATCH_QUEUE_PRIORITY_BACKGROUND,那麼當有I/O操作爭奪的時候,任何在這個佇列上通過dispatch_io_read或dispatch_io_write執行的I/O操作都會被壓制。

關於dispatch_source_tdispatch_io_t的用法會在以後的章節去介紹。

總結:這篇文章主要詳細的介紹了一下關於多執行緒的知識和GCD佇列的的建立,關於GCD的用法和其他方面的知識會在後續文章裡面去描述,給自己立一個flag,爭取完全弄懂以後寫出好的部落格來。

參考文件