Dispatch Queues調度隊列

前言-死鎖案例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

相關文章
相關標籤/搜索