原文連接:http://www.cnblogs.com/mddblog/p/4767559.html html
敲下gcd三個字母,搜狗第一條顯示竟然是「滾牀單」 ^_^ios
GCD,英文全稱是Grand Central Dispatch(功能強悍的中央調度器),基於C語言編寫的一套多線程開發機制,所以使用時會以函數形式出現,且大部分函數以dispatch開頭,雖然是C語言的但相對於蘋果其它多線程實現方式,抽象層次更高,使用起來也更加方便。git
它是蘋果爲應對多核的並行運算提出的解決方案,它會自動利用多核進行併發處理和運算,且線程由系統自動管理(調度、運行),無需程序員參與,使用起來很是方便。程序員
GCD有兩個核心:任務和隊列。github
任務:要執行的操做或方法函數,隊列:存聽任務的集合,而咱們要作的就是將任務添加到隊列而後執行,GCD會自動將隊列中的任務按先進先出的方式取出並交給對應線程執行。注意任務的取出是按照先進先出的方式,這也是隊列的特性,可是取出後的執行順序則不必定,下面會詳細討論。緩存
1 任務網絡
任務是一個比較抽象的概念,能夠簡單的認爲是一個操做、一個函數、一個方法等等,在實際的開發中大可能是以block(block使用詳見)的形式,使用起來也更加靈活。多線程
2 隊列queue併發
隊列建立,根據函數第二個參數來建立串行或並行隊列。框架
// 參數1 隊列名稱 // 參數2 隊列類型 DISPATCH_QUEUE_SERIAL/NULL串行隊列,DISPATCH_QUEUE_CONCURRENT表明並行隊列 // 下面代碼爲建立一個串行隊列,也是實際開發中用的最多的 dispatch_queue_t serialQ = dispatch_queue_create("隊列名", NULL);
全局隊列屬於並行隊列,只不過已由系統建立的沒有名字,且在全局可見(可用)。獲取全局隊列:
/* 取得全局隊列 第一個參數:線程優先級,設爲默認便可,我的習慣寫0,等同於默認 第二個參數:標記參數,目前沒有用,通常傳入0 */ serialQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
主隊列屬於串行隊列,也由系統建立,只不過運行在主線程(UI線程)。獲取主隊列:
// 獲取主隊列 serialQ = dispatch_get_main_queue();
queue屬於一個對象,也是佔用內存的,也會使用引用計數,當向queue添加一個任務時就會將這個queue retain一下,引用計數+1,直到全部任務都完成內存纔會釋放。(咱們在聲明一個queue屬性時要用strong)。
3 執行方式-2種
同步執行和異步執行。
同步執行:不會開啓新的線程,在當前線程執行。
異步執行:gcd管理的線程池中有空閒線程就會從隊列中取出任務執行,會開啓線程。
下面爲實現同步和異步的函數,函數功能爲:將任務添加到隊列並執行。
/* 同步執行 第一個參數:執行任務的隊列:串行、並行、全局、主隊列 第二個參數:block任務 */ void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block); // 異步執行 void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
注意:默認狀況下,新線程都沒有開啓runloop,因此當block任務完成後,線程都會自動被回收,假設咱們想在新開的線程中使用NSTimer,就必須開啓runloop,可使用[[NSRunLoop currentRunLoop] run]開啓當前線程,這是就要本身管理線程的回收等工做。
dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
加了一個barrier,意義在於:隊列以前的block處理完成以後纔開始處理隊列中barrier的block,且barrier的block必須處理完以後,才能處理其它的block。
根據這個特性咱們能夠實現123456一共6個block,可讓特定幾個併發執行完成以後,再併發執行剩下的block。好比123先併發,以後456再併發執行。具體代碼以下(將barrier放在123與456之間便可):
- (void)barrierTest { // 1 建立併發隊列 dispatch_queue_t BCqueue = dispatch_queue_create("BarrierConcurrent", DISPATCH_QUEUE_CONCURRENT); // 2.1 添加任務123 dispatch_async(BCqueue, ^{ NSLog(@"task1,%@", [NSThread currentThread]); }); dispatch_async(BCqueue, ^{ sleep(3); NSLog(@"task2,%@", [NSThread currentThread]); }); dispatch_async(BCqueue, ^{ sleep(1); NSLog(@"task3,%@", [NSThread currentThread]); }); // 2.2 添加barrier dispatch_barrier_async(BCqueue, ^{ NSLog(@"barrier"); }); // 2.3 添加任務456 dispatch_async(BCqueue, ^{ sleep(1); NSLog(@"task4,%@", [NSThread currentThread]); }); dispatch_async(BCqueue, ^{ NSLog(@"task5,%@", [NSThread currentThread]); }); dispatch_async(BCqueue, ^{ NSLog(@"task6,%@", [NSThread currentThread]); }); }
輸出結果,爲了顯示效果,代碼有延時操做:
22:33:56.822 GCDTest[32339:7753989] task1,<NSThread: 0x600000274780>{number = 3, name = (null)} 22:33:57.827 GCDTest[32339:7753986] task3,<NSThread: 0x600000271a00>{number = 5, name = (null)} 22:33:59.826 GCDTest[32339:7754004] task2,<NSThread: 0x600000275100>{number = 4, name = (null)} 22:33:59.826 GCDTest[32339:7754004] barrier 22:33:59.827 GCDTest[32339:7753986] task5,<NSThread: 0x600000271a00>{number = 5, name = (null)} 22:33:59.827 GCDTest[32339:7753987] task6,<NSThread: 0x608000279300>{number = 6, name = (null)} 22:34:00.828 GCDTest[32339:7754004] task4,<NSThread: 0x600000275100>{number = 4, name = (null)}
很明顯兩種執行方式,兩種隊列。那麼就有4種狀況:串行隊列同步執行、串行隊列異步執行、並行隊列同步執行、並行隊列異步執行。哪種會開啓新的線程?開幾條?是否併發?記憶起來比較繞,可是隻要抓住基本的就能夠,爲了方便理解,現分析以下:
1)串行隊列,同步執行-----串行隊列意味着順序執行,同步執行意味着不開啓線程(在當前線程執行)
2)串行隊列,異步執行-----串行隊列意味着任務順序執行,異步執行說明要開線程, (若是開多個線程的話,不能保證串行隊列順序執行,因此只開一個線程)
3)並行隊列,異步執行-----並行隊列意味着執行順序不肯定,異步執行意味着會開啓線程,而並行隊列又容許不按順序執行,因此係統爲了提升性能會開啓多個線程,來隊列取任務(隊列中任務取出仍然是順序取出的,只是線程執行無序)。
4)並行隊列,同步執行-----同步執行意味着不開線程,則確定是順序執行
5)死鎖-----程序執行不出來(死鎖) ;
這種死鎖最多見,問題也最嚴重,會形成主線程卡住。緣由:主隊列,若是主線程正在執行代碼,就不調度任務;同步執行:一直執行第一個任務直到結束。二者互相等待形成死鎖,示例以下:
- (void)mainThreadDeadLockTest { NSLog(@"begin"); dispatch_sync(dispatch_get_main_queue(), ^{ // 發生死鎖下面的代碼不會執行 NSLog(@"middle"); }); // 發生死鎖下面的代碼不會執行,固然函數也不會返回,後果也最爲嚴重 NSLog(@"end"); }
緣由:serialQueue爲串行隊列,當代碼執行到block1時正常,執行到dispatch_sync時,dispatch_sync等待block2執行完畢纔會返回,而serialQueue是串行隊列,它正在執行block1,只有等block1執行完畢後纔會去執行block2,相互等待形成死鎖
- (void)deadLockTest { // 其它線程的死鎖 dispatch_queue_t serialQueue = dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL); dispatch_async(serialQueue, ^{ // 串行隊列block1 NSLog(@"begin"); dispatch_sync(serialQueue, ^{ // 串行隊列block2 發生死鎖,下面的代碼不會執行 NSLog(@"middle"); }); // 不會打印 NSLog(@"end"); }); // 函數會返回,不影響主線程 NSLog(@"return"); }
1 線程間通信
好比,爲了提升用戶體驗,咱們通常在其餘線程(非主線程)下載圖片或其它網絡資源,下載完成後咱們要更新UI,而UI更新必須在主線程執行,因此咱們常常會使用:
// 同步執行,會阻塞指導下面block中的代碼執行完畢 dispatch_sync(dispatch_get_main_queue(), ^{ // 主線程,UI更新 }); // 異步執行 dispatch_async(dispatch_get_main_queue(), ^{ // 主線程,UI更新 });
2 信號量的使用
也屬於線程間通信,下面的舉例是常常用到的場景。在網絡訪問中,NSURLSession類都是異步的(找了好久沒有找到同步的方法),而有時咱們但願可以像NSURLConnection同樣能夠同步訪問,即在網絡block調用完成以後作一些操做。那咱們可使用dispatch的信號量來解決:
/// 用於線程間通信,下面是等待一個網絡完成 - (void)dispatchSemaphore { NSString *urlString = [@"https://www.baidu.com" stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; // 設置緩存策略爲每次都從網絡加載 超時時間30秒 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30]; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { // 處理完成以後,發送信號量 NSLog(@"正在處理..."); dispatch_semaphore_signal(semaphore); }] resume]; // 等待網絡處理完成 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"處理完成!"); }
在上面的舉例中
dispatch_semaphore_signal
的調用必須是在另外一個線程調用,由於當前線程已經dispatch_semaphore_wait
阻塞。另外,dispatch_semaphore_wait
最好不要在主線程調用
3 其它經常使用
全局隊列,實現併發:
dispatch_async(dispatch_get_global_queue(0, 0), ^{ // 要執行的代碼 });
使用調度組,能夠輕鬆實如今一些任務完成後,作一些操做。好比具備順序性要求的生產者消費者等等。
示例1 任務1完成以後執行任務2。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self groupTest]; } - (void)groupTest { // 建立一個組 dispatch_group_t group = dispatch_group_create(); NSLog(@"開始執行"); dispatch_async(dispatch_get_global_queue(0, 0), ^{ dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ // 任務1 // 等待1s一段時間在執行 [NSThread sleepForTimeInterval:1]; NSLog(@"task1 running in %@",[NSThread currentThread]); }); dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{ // 任務2 NSLog(@"task2 running in %@",[NSThread currentThread]); }); }); }
點擊屏幕後,打印以下,能夠看到任務1雖然等待了1s,任務2也不執行,只有任務1執行完畢才執行任務2.
2015-08-28 18:16:05.317 GCDTest[1468:229374] 開始執行 2015-08-28 18:16:06.323 GCDTest[1468:229457] task1 running in <NSThread: 0x7f8962f16900>{number = 2, name = (null)} 2015-08-28 18:16:06.323 GCDTest[1468:229456] task2 running in <NSThread: 0x7f8962c92750>{number = 3, name = (null)}
示例2,其實示例1並不經常使用,真正用到的是監控多個任務完成以後,回到主線程更新UI,或者作其它事情。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self groupTest]; } - (void)groupTest { // 建立一個組 dispatch_group_t group = dispatch_group_create(); NSLog(@"開始執行"); dispatch_async(dispatch_get_global_queue(0, 0), ^{ dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ // 關聯任務1 NSLog(@"task1 running in %@",[NSThread currentThread]); }); dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ // 關聯任務2 NSLog(@"task2 running in %@",[NSThread currentThread]); }); dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ // 關聯任務3 NSLog(@"task3 running in %@",[NSThread currentThread]); }); dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ // 關聯任務4 // 等待1秒 [NSThread sleepForTimeInterval:1]; NSLog(@"task4 running in %@",[NSThread currentThread]); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 回到主線程執行 NSLog(@"mainTask running in %@",[NSThread currentThread]); }); }); }
點擊屏幕後,打印以下,能夠看到不管其它任務而後和執行,mainTask等待它們執行後才執行。
2015-08-28 18:24:14.312 GCDTest[1554:236273] 開始執行 2015-08-28 18:24:14.312 GCDTest[1554:236352] task3 running in <NSThread: 0x7fa8f1f0c9c0>{number = 4, name = (null)} 2015-08-28 18:24:14.312 GCDTest[1554:236354] task1 running in <NSThread: 0x7fa8f1d10750>{number = 2, name = (null)} 2015-08-28 18:24:14.312 GCDTest[1554:236351] task2 running in <NSThread: 0x7fa8f1c291a0>{number = 3, name = (null)} 2015-08-28 18:24:15.313 GCDTest[1554:236353] task4 running in <NSThread: 0x7fa8f1d0e7f0>{number = 5, name = (null)} 2015-08-28 18:24:15.313 GCDTest[1554:236273] mainTask running in <NSThread: 0x7fa8f1c13df0>{number = 1, name = main}
根據上面的代碼,能夠看出有關dispatch的對象並非OC對象,那麼,用不用像對待Core Foundation
框架的對象同樣,使用retain/release
來管理呢?答案是不用的!
若是是ARC環境,咱們無需管理,會像對待OC對象同樣自動內存管理。
若是是MRC環境,不是使用retain/release
,而是使用dispatch_retain/dispatch_release
來管理。
測試用例github下載:https://github.com/mddios/GCDTest