前言-死鎖案例html
// 在主線程中執行 dispatch_queue_t queueMain = dispatch_get_main_queue(); dispatch_sync(queueMain, ^{ NSLog(@"+++++++"); }); NSLog(@"hahahaha");
案例分析:運行結果是程序阻塞在dispatch_sync()處。因爲main線程執行到dispatch_sync()處,線程處於等待狀態。將block任務塊添加到主串行隊列最後,block等待當前任務(即正在主線程中執行的任務)執行完畢,而當前任務由於阻塞沒法結束,致使兩邊都在等待,因此出現死鎖。緩存
1、簡介安全
Dispatch queues是一種異步和併發執行任務的簡單方式,將任務經過function或者block object的方式添加到dispatch queue。使用Dispatch queue仍是使用thread?Dispatch queue中只須要封裝任務,再將任務加到合適的Disptch queue,由系統去管理線程。若是用thread,須要作的工做就多了。使用Dispatch queue實現同步,實現效果比加鎖要有效。數據結構
由dispatch queue管理提交的任務,全部任務聽從先進先出的規則,先添加到dispatch queue中的任務會最早開始執行。GCD提供了一些現成的dispatch queue,或者也能夠本身定製dispatch queue。dispatch queue可分爲3類:併發
一、Serial queues串行隊列app
serial queues(private dispatch queue)按dispatch queue中的順序一個時間執行一個任務,經常使用於同步的獲取指定資源。你能夠隨意建立serial queues,多個serial queues之間是並行的。換句話說,建立了4個serial queues,單個queue中的任務串行,queue與queue中的任務是並行的。異步
二、Concurrent queues併發隊列async
concurrent queues(global dispatch queue)併發的執行一個或多個任務,但也是按照先進先出的順序開始啓動任務。在某個時間點,可執行任務數量的上限取決於系統狀態。在iOS5之後,能夠經過函數dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)獲取4中queue,也能夠經過如下代碼建立:ide
dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent", DISPATCH_QUEUE_CONCURRENT)
三、Main dispatch queue主隊列函數
main dispatch queue(globally available serial queue)中的任務在主線程上串行執行。此queue和應用的run loop配合工做,主線程中在執行run loop的事件源後,插入queue任務。main dispatch queue爲應用的主要同步queue。儘管不須要本身建立main dispatch queue,但使用時要注意。
2、與Dispatch Queues相關的技術
一、Dispatch groups
二、Dispatch semaphores
三、Dispatch sources(specific types of system events)
3、Block任務
一、block使用準則:
1)dispatch queue中的blocks異步地執行,最好在block只捕捉上下文中純變量,不要試圖捕捉結構體或者其它基於指針的變量,這些變量由調用上下文來分配和回收。緣由是在block執行中,捕獲的變量內存可能已經被回收。固然有解決方案,好比爲該對象分配內存,而後blcok指向並retain當前內存。
2)dispatch queue會copy添加到其中的block。等任務結束再釋放block。換句話說,在添加到queue時不須要明確寫copy代碼。
3)儘管在執行小任務時,queues調度執行任務比原始的threads更有效,但調度和執行block仍然有開銷。若是block中的任務輕,直接執行反而更高效。
4)不要緩存與基礎線程相關的數據,不要試圖從其它的block中獲取數據。若是但願同一個queue中的數據共享,使用dispatch queue的context指針來實現。
5)若是block內部建立了不少的OC對象,將block代碼加到@autorelease塊中,這樣能更及時釋放再也不使用的對象。儘管dispatch queue中帶有自動釋放池,但不保證會及時回收那些OC對象。
4、建立和管理Dispatch Queue
一、獲取Global Concurrent Dispatch Queues。
每一個應用程序有4個global concurrent dispatch queues,他們之間只有優先級的區別,包括的優先級有:DISPATCH_QUEUE_PRIORITY_HIGHDISPATCH_QUEUE_PRIORITY_DEFAULT、DISPATCH_QUEUE_PRIORITY_LOW、DISPATCH_QUEUE_PRIORITY_BACKGROUND。儘管dispatch queue存在引用計數,但不須要retain/release該隊列,由於他們在應用中是全局可訪問的,只須要經過方法獲取該queue便可,代碼以下:
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
二、建立Serial Dispatch Queues
dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);
第一個參數是隊列的名稱,第二個暫時固定爲NULL。
三、獲取runtime的Common Queues
1)dispatch_get_current_queue()獲取當前隊列。若是在block內部調用,則返回的是block添加到的queue。若是是block外部調用,則返回當前queue。
2)dispatch_get_main_queue()獲取主線程隊列。
3)dispatch_get_global_queue()獲取全局併發隊列。
四、Dispatch Queue的內存管理
併發隊列和主隊列都不須要考慮內存管理的問題,只須要考慮本身定義的串行隊列。經過dispatch_retain和dispatch_release方法增長和減小隊列對象的引用計數器。
見到過在dealloc方法中,調用dispatch_release釋放自定義的串行隊列。
五、Dispatch Queue中數據存儲-Context
dispatch queue能經過dispatch_set_context函數關聯context data,經過dispatch_get_context方法來獲取context data,context data存儲了指向OC對象或者是數據結構的指針。context data須要手動分配和釋放內存,可以使用queue的finalizer函數完成釋放。代碼以下:
dispatch_queue_t storeData() { MyDataContext *contextData = (MyDataContext*) malloc(sizeof(MyDataContext)); myInitializeDataContextFunction(contextData); // 建立串行隊列,設置context data dispatch_queue_t serialQueue = dispatch_queue_create("com.example.CriticalTaskQueue", NULL); dispatch_set_context(serialQueue, contextData); // 回收context data dispatch_set_finalizer_f(serialQueue, &myFinalizerFunction); return serialQueue; } void myFinalizerFunction(void *context) { MyDataContext* contextData = (MyDataContext*)context; // 清理內容結構 myCleanUpDataContextFunction(contextData); // 釋放 free(contextData);
}
5、往Dispatch Queues中添加任務
一、添加單一任務
dispatch_sync()同步調用,會阻塞方法所在線程。
dispatch_async()異步調用,不會阻塞線程。
dispatch_barrier_async()。等待前面的任務所有結束,該task才能執行,而後後面的任務才能啓動執行
dispatch_once(&onceToken,^{}); // static dispatch_once_t onceToken;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2.0*NSEC_PER_SEC)),dispatch_get_main_queue(), ^{});// 將2秒後需執行的任務......
二、Completion Block
在dispatch queue中的某個任務執行結束後,通知作些後續操做。不一樣於回調機制,dispatch queue採用completion block實現。具體操做是,在其任務最後添加將completion block提交到指定queue中。
// myBlock和myQueue分別就是completion block和提交至的queue void sum_async(int data01, int data02, dispatch_queue_t myQueue, void (^myBlock)(int)) { // Retain the queue provided by the user to make sure it does not disappear before the completion block can be called. dispatch_retain(myQueue); // Do the work on the default concurrent queue, then call the user-provided block with the results. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ int sumValue = data + len; dispatch_async(myQueue, ^{ myBlock(sumValue); }); // Release the user-provided queue when done dispatch_release(myQueue); }); }
三、併發執行循環迭代dispatch_apply()
在執行指定次數的循環中,且單個循環間彼此獨立、執行的前後順序可有可無。這時不該該再採用最初的for循環依次遍歷,而應考慮經過concurrent dispatch queue提升性能。有個函數dispatch_apply(),能一次性的將全部循環提交給queue,當提交給concurrent queue時,就能同一時間執行多個循環。
固然也能夠將任務提交到serial queue,但沒什麼優點和意義,不建議。
注意:跟普通的循環同樣,dispatch_apply函數需等全部的循環運行結束後纔會返回。由於會堵塞當前線程,在主線程中要慎用,避免執行全部循環阻礙主線程及時響應事件。若是循環要求比較長的處理時間,你應該在其它線程上調用dispatch_apply。
避免出現死鎖場景:執行dispatch_apply所在線程的queue和入參中的queue是同一個,且queue是serial queue,那麼線程執行到dispatch_apply函數時,需等待全部循環運行結束,而serial queue中的任務又只執行到dispatch_apply,得等當前任務執行完畢,雙方互相等待彼此結束任務。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // count是總共循環次數,i是當前循環數 dispatch_apply(count, queue, ^(size_t i) { printf("%u\n",i); });
四、在主線程上執行任務
GCD提供的一個特殊的main dispatch queue,其中的任務在主線程中串行。可經過dispatch_get_main_queue函數可獲取該queue,它由應用自動提供且自動回收,給主線程建立了一個run loop。
若是建立的不是cocoa應用程序,也不想明確建立run loop,那必須調用dispatch_main函數去明確執行main dispatch queue中的任務,不然,queue中的任務永遠不會執行。
五、在任務中使用OC對象
GCD創建在cocoa內存管理的基礎上,能夠在任務中任意使用OC對象,每個dispatch queue都擁有本身的autorelease pool。但若是應用內存要求嚴格,且在任務中添加了不少的autoreleased對象,想要及時釋放這些對象,建立本身的autorelease pool。
6、暫停和恢復Queue
經過調用dispatch_suspend函數暫時中止queue中的任務執行,調用dispatch_resume函數恢復執行。有一個suspension reference count,當暫停時該引用計數增長,當恢復時引用計數減小,爲0時,queue是暫停狀態。所以,必須平衡調用suspend和resume方法。
注意:這兩個方法都是異步執行,在queue中任務之間生效,也就是說暫停不會讓正在執行的任務停止,停止是下一個任務。
七、使用Dispatch Semaphore來有效使用有限資源
給某個有限資源,指定能同時訪問的最大數量,能夠經過dispatch semaphore實現。dispatch semaphore跟大多數semaphore同樣,除了獲取dispatch semaphore比系統的semaphore要快。這是由於GCD通常不訪問內核,只有在資源不可用時,纔會去內核調用,系統暫停線程以待semaphore發出信號。使用以下:
一、經過dispatch_semaphore_create函數建立semaphore,能夠選擇指定一個正整數來顯示一次可訪問上限。
二、在每一個任務,經過dispatch_semaphore_wait函數等待信號量。
三、當信號量發出信號,就能獲取資源執行任務。
四、當使用資源結束後,經過dispatch_semaphore_signal函數釋放信號併發出信號。
這些步驟如何工做可參考系統的file descriptor。每一個應用提供了有限的file descriptors,若是某個任務是處理大量的文件,你不想一次打開這麼多文件以致於耗盡file descriptors,這時,就能夠採用semaphore去限制使用file descriptors的數量,使用部分代碼以下:
// Create the semaphore, specifying the initial pool size dispatch_semaphore_t fd_sema = dispatch_semaphore_create(getdtablesize() / 2); // Wait for a free file descriptor dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER); fd = open("/etc/services", O_RDONLY); // Release the file descriptor when done close(fd); dispatch_semaphore_signal(fd_sema);
8、Group Queue組任務隊列
dispatch group阻塞線程直到一個或多個任務執行結束。可用的場景是等待全部指定任務完成後,再執行某任務。另外一個相似於使用dispatch group的是thread join,二者實現方式不一樣。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); // Add a task to the group dispatch_group_async(group, queue, ^{ // Some asynchronous work }); // Do some other work while the tasks execute. // When you cannot make any more forward progress, // wait on the group to block the current thread. dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // Release the group when it is no longer needed. dispatch_release(group);
9、Dispatch queues和線程安全
任什麼時候候實現併發,都須要知道如下幾點:
一、dispatch queue自己是線程安全的,換句話說,能夠從任何線程提交任務到queue中,不須要考慮queue的鎖和同步問題。
二、dispatch_sync調用所在的queueA,與入參queueB,若是queueA和queueB是同一個queue,那麼會形成死鎖的狀況。使用dispatch_async函數就不會。
三、避免在任務中使用鎖。若是是串行隊列,若是獲取不到鎖,會阻塞整個隊列中的任務執行。若是是並行隊列,獲取不到鎖,會阻塞隊列中其它任務的執行。因此須要同步執行代碼,使用serial dispatch queue代替鎖。
四、儘管能夠獲取任務所在基礎線程信息,最好不要這樣作。
10、該博客翻譯自蘋果官方文檔
https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW8