細說GCD(Grand Central Dispatch)如何用

ming1016 edited this page on 18 Jan ·  2 revisions

文中較詳細介紹GCD隊列,各類GCD使用方法,實例如何使用Dispatch Source監聽系統底層對象,分析不一樣鎖的性能對比,實例GCD死鎖狀況。文中的Demo在這裏https://github.com/ming1016/GCDDemo 對着文章試着來調demo體會更深哦,細細嚼消化好:)git

GCD(Grand Central Dispatch) 介紹

GCD屬於系統級的線程管理,在Dispatch queue中執行須要執行的任務性能很是的高。GCD這塊已經開源,地址http://libdispatch.macosforge.org。GCD中的FIFO隊列稱爲dispatch queue,用來保證先進來的任務先獲得執行。github

GCD概要

  • 和operation queue同樣都是基於隊列的併發編程API,他們經過集中管理你們協同使用的線程池。
  • 公開的5個不一樣隊列:運行在主線程中的main queue,3個不一樣優先級的後臺隊列(High Priority Queue,Default Priority Queue,Low Priority Queue),以及一個優先級更低的後臺隊列Background Priority Queue(用於I/O)
  • 可建立自定義隊列:串行或並列隊列。自定義通常放在Default Priority Queue和Main Queue裏。
  • 操做是在多線程上仍是單線程主要是看隊列的類型和執行方法,並行隊列異步執行才能在多線程,並行隊列同步執行就只會在主線程執行了

基本概念

  • 系統標準兩個隊列
//全局隊列,一個並行的隊列
dispatch_get_global_queue
//主隊列,主線程中的惟一隊列,一個串行隊列 dispatch_get_main_queue
  • 自定義隊列
//串行隊列
dispatch_queue_create("com.starming.serialqueue", DISPATCH_QUEUE_SERIAL) //並行隊列 dispatch_queue_create("com.starming.concurrentqueue", DISPATCH_QUEUE_CONCURRENT)
  • 同步異步線程建立
//同步線程
dispatch_sync(..., ^(block)) //異步線程 dispatch_async(..., ^(block))

隊列(dispatch queue)

  • Serial:又叫private dispatch queues,同時只執行一個任務。Serial queue經常使用於同步訪問特定的資源或數據。當你建立多個Serial queue時,雖然各自是同步,但serial queue之間是併發執行。
  • Main dispatch queue:全局可用的serial queue,在應用程序主線程上執行任務。
  • Concurrent:又叫global dispatch queue,能夠併發的執行多個任務,但執行完成順序是隨機的。系統提供四個全局併發隊列,這四個隊列有這對應的優先級,用戶是不可以建立全局隊列的,只能獲取。
dipatch_queue_t queue;
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
  • user create queue:建立本身定義的隊列,能夠用dispatch_queue_create函數,函數有兩個參數,第一個自定義的隊列名,第二個參數是隊列類型,默認NULL或者DISPATCH_QUEUE_SERIAL的是串行,參數爲DISPATCH_QUEUE_CONCURRENT爲並行隊列。
dispatch_queue_t queue
queue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue", DISPATCH_QUEUE_CONCURRENT);
  • 自定義隊列的優先級:能夠經過dipatch_queue_attr_make_with_qos_class或dispatch_set_target_queue方法設置隊列的優先級
//dipatch_queue_attr_make_with_qos_class
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1); dispatch_queue_t queue = dispatch_queue_create("com.starming.gcddemo.qosqueue", attr); //dispatch_set_target_queue dispatch_queue_t queue = dispatch_queue_create("com.starming.gcddemo.settargetqueue",NULL); //須要設置優先級的queue dispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //參考優先級 dispatch_set_target_queue(queue, referQueue); //設置queue和referQueue的優先級同樣
  • dispatch_set_target_queue:能夠設置優先級,也能夠設置隊列層級體系,好比讓多個串行和並行隊列在統一一個串行隊列裏串行執行,以下
dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL); dispatch_queue_t firstQueue = dispatch_queue_create("com.starming.gcddemo.firstqueue", DISPATCH_QUEUE_SERIAL); dispatch_queue_t secondQueue = dispatch_queue_create("com.starming.gcddemo.secondqueue", DISPATCH_QUEUE_CONCURRENT); dispatch_set_target_queue(firstQueue, serialQueue); dispatch_set_target_queue(secondQueue, serialQueue); dispatch_async(firstQueue, ^{ NSLog(@"1"); [NSThread sleepForTimeInterval:3.f]; }); dispatch_async(secondQueue, ^{ NSLog(@"2"); [NSThread sleepForTimeInterval:2.f]; }); dispatch_async(secondQueue, ^{ NSLog(@"3"); [NSThread sleepForTimeInterval:1.f]; });

隊列類型

隊列默認是串行的,若是設置改參數爲NULL會按串行處理,只能執行一個單獨的block,隊列也能夠是並行的,同一時間執行多個blockchrome

- (id)init;
{
     self = [super init]; if (self != nil) { NSString *label = [NSString stringWithFormat:@"%@.isolation.%p", [self class], self]; self.isolationQueue = dispatch_queue_create([label UTF8String], 0); label = [NSString stringWithFormat:@"%@.work.%p", [self class], self]; self.workQueue = dispatch_queue_create([label UTF8String], 0); } return self; }

5種隊列,主隊列(main queue),四種通用調度隊列,本身定製的隊列。四種通用調度隊列爲數據庫

  • QOS_CLASS_USER_INTERACTIVE:user interactive等級表示任務須要被當即執行提供好的體驗,用來更新UI,響應事件等。這個等級最好保持小規模。
  • QOS_CLASS_USER_INITIATED:user initiated等級表示任務由UI發起異步執行。適用場景是須要及時結果同時又能夠繼續交互的時候。
  • QOS_CLASS_UTILITY:utility等級表示須要長時間運行的任務,伴有用戶可見進度指示器。常常會用來作計算,I/O,網絡,持續的數據填充等任務。這個任務節能。
  • QOS_CLASS_BACKGROUND:background等級表示用戶不會察覺的任務,使用它來處理預加載,或者不須要用戶交互和對時間不敏感的任務。

示例:後臺加載顯示圖片macos

override func viewDidLoad() { super.viewDidLoad() dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) { // 將工做從主線程轉移到全局隊列中,這是dispatch_async調用,異步提交保證調用線程會繼續執行下去,這樣viewDidLoad在主線程上可以更早完成, let overlayImage = self.faceOverlayImageFromImage(self.image) dispatch_async(dispatch_get_main_queue()) { // 新圖完成,把一個閉包加入主線程用來更新UIImageView,只有在主線程能操做UIKit。 self.fadeInNewImage(overlayImage) // 更新UI } } }

什麼時候使用何種隊列類型編程

  • 主隊列(順序):隊列中有任務完成須要更新UI時,dispatch_after在這種類型中使用。
  • 併發隊列:用來執行與UI無關的後臺任務,dispatch_sync放在這裏,方便等待任務完成進行後續處理或和dispatch barrier同步。dispatch groups放在這裏也不錯。
  • 自定義順序隊列:順序執行後臺任務並追蹤它時。這樣作同時只有一個任務在執行能夠防止資源競爭。dipatch barriers解決讀寫鎖問題的放在這裏處理。dispatch groups也是放在這裏。

可使用下面的方法簡化QoS等級參數的寫法swift

var GlobalMainQueue: dispatch_queue_t {
     return dispatch_get_main_queue() } var GlobalUserInteractiveQueue: dispatch_queue_t { return dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.value), 0) } var GlobalUserInitiatedQueue: dispatch_queue_t { return dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0) } var GlobalUtilityQueue: dispatch_queue_t { return dispatch_get_global_queue(Int(QOS_CLASS_UTILITY.value), 0) } var GlobalBackgroundQueue: dispatch_queue_t { return dispatch_get_global_queue(Int(QOS_CLASS_BACKGROUND.value), 0) } //使用起來就是這樣,易讀並且容易看出在使用哪一個隊列 dispatch_async(GlobalUserInitiatedQueue) { let overlayImage = self.faceOverlayImageFromImage(self.image) dispatch_async(GlobalMainQueue) { self.fadeInNewImage(overlayImage) } }

dispatch_once用法

dispatch_once_t要是全局或static變量,保證dispatch_once_t只有一份實例api

+ (UIColor *)boringColor;
{
     static UIColor *color;
     //只運行一次 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ color = [UIColor colorWithRed:0.380f green:0.376f blue:0.376f alpha:1.000f]; }); return color; }

dispatch_async

設計一個異步的API調用dispatch_async(),這個調用放在API的方法或函數中作。讓API的使用者設置一個回調處理隊列數組

- (void)processImage:(UIImage *)image completionHandler:(void(^)(BOOL success))handler; { dispatch_async(self.isolationQueue, ^(void){ // do actual processing here dispatch_async(self.resultQueue, ^(void){ handler(YES); }); }); }

能夠避免界面會被一些耗時的操做卡死,好比讀取網絡數據,大數據IO,還有大量數據的數據庫讀寫,這時須要在另外一個線程中處理,而後通知主線程更新界面,GCD使用起來比NSThread和NSOperation方法要簡單方便。

//代碼框架
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 耗時的操做 dispatch_async(dispatch_get_main_queue(), ^{ // 更新界面 }); }); //下載圖片的示例 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"]; NSData * data = [[NSData alloc]initWithContentsOfURL:url]; UIImage *image = [[UIImage alloc]initWithData:data]; if (data != nil) { dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); } });

dispatch_after延後執行

dispatch_after只是延時提交block,不是延時馬上執行。

- (void)foo
{
     double delayInSeconds = 2.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ [self bar]; }); }

範例,實現一個推遲出現彈出框提示,好比說提示用戶評價等功能。

func showOrHideNavPrompt() { let delayInSeconds = 1.0 let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delayInSeconds * Double(NSEC_PER_SEC))) // 在這裏聲明推遲的時間 dispatch_after(popTime, GlobalMainQueue) { // 等待delayInSeconds將閉包異步到主隊列 let count = PhotoManager.sharedManager.photos.count if count > 0 { self.navigationItem.prompt = nil } else { self.navigationItem.prompt = "Add photos with faces to Googlyify them!" } } }

例子中的dispatch time的參數,能夠先看看函數原型

dispatch_time_t dispatch_time ( dispatch_time_t when, int64_t delta );

第一個參數爲DISPATCH_TIME_NOW表示當前。第二個參數的delta表示納秒,一秒對應的納秒爲1000000000,系統提供了一些宏來簡化

 #define NSEC_PER_SEC 1000000000ull //每秒有多少納秒 #define USEC_PER_SEC 1000000ull //每秒有多少毫秒 #define NSEC_PER_USEC 1000ull //每毫秒有多少納秒

這樣若是要表示一秒就能夠這樣寫

dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC); dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC); dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC);

dispatch_barrier_async使用Barrier Task方法Dispatch Barrier解決多線程併發讀寫同一個資源發生死鎖

Dispatch Barrier確保提交的閉包是指定隊列中在特定時段惟一在執行的一個。在全部先於Dispatch Barrier的任務都完成的狀況下這個閉包纔開始執行。輪到這個閉包時barrier會執行這個閉包而且確保隊列在此過程不會執行其它任務。閉包完成後隊列恢復。須要注意dispatch_barrier_async只在本身建立的隊列上有這種做用,在全局併發隊列和串行隊列上,效果和dispatch_sync同樣

//建立隊列
self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT); //改變setter - (void)setCount:(NSUInteger)count forKey:(NSString *)key { key = [key copy]; //確保全部barrier都是async異步的 dispatch_barrier_async(self.isolationQueue, ^(){ if (count == 0) { [self.counts removeObjectForKey:key]; } else { self.counts[key] = @(count); } }); } - (void)dispatchBarrierAsyncDemo { //防止文件讀寫衝突,能夠建立一個串行隊列,操做都在這個隊列中進行,沒有更新數據讀用並行,寫用串行。 dispatch_queue_t dataQueue = dispatch_queue_create("com.starming.gcddemo.dataqueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(dataQueue, ^{ [NSThread sleepForTimeInterval:2.f]; NSLog(@"read data 1"); }); dispatch_async(dataQueue, ^{ NSLog(@"read data 2"); }); //等待前面的都完成,在執行barrier後面的 dispatch_barrier_async(dataQueue, ^{ NSLog(@"write data 1"); [NSThread sleepForTimeInterval:1]; }); dispatch_async(dataQueue, ^{ [NSThread sleepForTimeInterval:1.f]; NSLog(@"read data 3"); }); dispatch_async(dataQueue, ^{ NSLog(@"read data 4"); }); }

swift示例

//使用dispatch_queue_create初始化一個併發隊列。第一個參數遵循反向DNS命名習慣,方便描述,第二個參數是指出是併發仍是順序。
private let concurrentPhotoQueue = dispatch_queue_create( "com.raywenderlich.GooglyPuff.photoQueue", DISPATCH_QUEUE_CONCURRENT) func addPhoto(photo: Photo) { dispatch_barrier_async(concurrentPhotoQueue) { // 將寫操做加入到自定義的隊列。開始執行時這個就是隊列中惟一的一個在執行的任務。 self._photos.append(photo) // barrier可以保障不會和其餘任務同時進行。 dispatch_async(GlobalMainQueue) { // 涉及到UI因此這個通知應該在主線程中,因此分派另外一個異步任務到主隊列中。 self.postContentAddedNotification() } } } //上面是解決了寫可能發生死鎖,下面是使用dispatch_sync解決讀時可能會發生的死鎖。 var photos: [Photo] { var photosCopy: [Photo]! dispatch_sync(concurrentPhotoQueue) { // 同步調度到concurrentPhotoQueue隊列執行讀操做 photosCopy = self._photos // 保存 } return photosCopy } //這樣讀寫問題都解決了。

都用異步處理避免死鎖,異步的缺點在於調試不方便,可是比起同步容易產生死鎖這個反作用還算小的。

dispatch_apply進行快速迭代

相似for循環,可是在併發隊列的狀況下dispatch_apply會併發執行block任務。

for (size_t y = 0; y < height; ++y) { for (size_t x = 0; x < width; ++x) { // Do something with x and y here } } //由於能夠並行執行,因此使用dispatch_apply能夠運行的更快 - (void)dispatchApplyDemo { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue", DISPATCH_QUEUE_CONCURRENT); dispatch_apply(10, concurrentQueue, ^(size_t i) { NSLog(@"%zu",i); }); NSLog(@"The end"); //這裏有個須要注意的是,dispatch_apply這個是會阻塞主線程的。這個log打印會在dispatch_apply都結束後纔開始執行 }

dispatch_apply能避免線程爆炸,由於GCD會管理併發

- (void)dealWiththreadWithMaybeExplode:(BOOL)explode { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); if (explode) { //有問題的狀況,可能會死鎖 for (int i = 0; i < 999 ; i++) { dispatch_async(concurrentQueue, ^{ NSLog(@"wrong %d",i); //do something hard }); } } else { //會優化不少,可以利用GCD管理 dispatch_apply(999, concurrentQueue, ^(size_t i){ NSLog(@"correct %zu",i); //do something hard }); } }

示例:

func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) { var storedError: NSError! var downloadGroup = dispatch_group_create() let addresses = [OverlyAttachedGirlfriendURLString, SuccessKidURLString, LotsOfFacesURLString] dispatch_apply(UInt(addresses.count), GlobalUserInitiatedQueue) { i in let index = Int(i) let address = addresses[index] let url = NSURL(string: address) dispatch_group_enter(downloadGroup) let photo = DownloadPhoto(url: url!) { image, error in if let error = error { storedError = error } dispatch_group_leave(downloadGroup) } PhotoManager.sharedManager.addPhoto(photo) } dispatch_group_notify(downloadGroup, GlobalMainQueue) { if let completion = completion { completion(error: storedError) } } }

Block組合Dispatch_groups

dispatch groups是專門用來監視多個異步任務。dispatch_group_t實例用來追蹤不一樣隊列中的不一樣任務。

當group裏全部事件都完成GCD API有兩種方式發送通知,第一種是dispatch_group_wait,會阻塞當前進程,等全部任務都完成或等待超時。第二種方法是使用dispatch_group_notify,異步執行閉包,不會阻塞。

第一種使用dispatch_group_wait的swift的例子:

func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) { dispatch_async(GlobalUserInitiatedQueue) { // 由於dispatch_group_wait會租塞當前進程,因此要使用dispatch_async將整個方法要放到後臺隊列纔可以保證主線程不被阻塞 var storedError: NSError! var downloadGroup = dispatch_group_create() // 建立一個dispatch group for address in [OverlyAttachedGirlfriendURLString, SuccessKidURLString, LotsOfFacesURLString] { let url = NSURL(string: address) dispatch_group_enter(downloadGroup) // dispatch_group_enter是通知dispatch group任務開始了,dispatch_group_enter和dispatch_group_leave是成對調用,否則程序就崩潰了。 let photo = DownloadPhoto(url: url!) { image, error in if let error = error { storedError = error } dispatch_group_leave(downloadGroup) // 保持和dispatch_group_enter配對。通知任務已經完成 } PhotoManager.sharedManager.addPhoto(photo) } dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER) // dispatch_group_wait等待全部任務都完成直到超時。若是任務完成前就超時了,函數會返回一個非零值,能夠經過返回值判斷是否超時。也能夠用DISPATCH_TIME_FOREVER表示一直等。 dispatch_async(GlobalMainQueue) { // 這裏能夠保證全部圖片任務都完成,而後在main queue里加入完成後要處理的閉包,會在main queue裏執行。 if let completion = completion { // 執行閉包內容 completion(error: storedError) } } } }

oc例子

- (void)dispatchGroupWaitDemo {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); //在group中添加隊列的block dispatch_group_async(group, concurrentQueue, ^{ [NSThread sleepForTimeInterval:2.f]; NSLog(@"1"); }); dispatch_group_async(group, concurrentQueue, ^{ NSLog(@"2"); }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"go on"); }

第二種使用dispatch_group_notify的swift的例子:

func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) { // 不用加dispatch_async,由於沒有阻塞主進程 var storedError: NSError! var downloadGroup = dispatch_group_create() for address in [OverlyAttachedGirlfriendURLString, SuccessKidURLString, LotsOfFacesURLString] { let url = NSURL(string: address) dispatch_group_enter(downloadGroup) let photo = DownloadPhoto(url: url!) { image, error in if let error = error { storedError = error } dispatch_group_leave(downloadGroup) } PhotoManager.sharedManager.addPhoto(photo) } dispatch_group_notify(downloadGroup, GlobalMainQueue) { // dispatch_group_notify和dispatch_group_wait的區別就是是異步執行閉包的,當dispatch groups中沒有剩餘的任務時閉包才執行。這裏是指明在主隊列中執行。 if let completion = completion { completion(error: storedError) } } }

oc例子

//dispatch_group_notify
- (void)dispatchGroupNotifyDemo { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, concurrentQueue, ^{ NSLog(@"1"); }); dispatch_group_async(group, concurrentQueue, ^{ NSLog(@"2"); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"end"); }); NSLog(@"can continue"); } //dispatch_group_wait - (void)dispatchGroupWaitDemo { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); //在group中添加隊列的block dispatch_group_async(group, concurrentQueue, ^{ [NSThread sleepForTimeInterval:2.f]; NSLog(@"1"); }); dispatch_group_async(group, concurrentQueue, ^{ NSLog(@"2"); }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"can continue"); } 

如何對現有API使用dispatch_group_t

//給Core Data的-performBlock:添加groups。組合完成任務後使用dispatch_group_notify來運行一個block便可。
- (void)withGroup:(dispatch_group_t)group performBlock:(dispatch_block_t)block { if (group == NULL) { [self performBlock:block]; } else { dispatch_group_enter(group); [self performBlock:^(){ block(); dispatch_group_leave(group); }]; } } //NSURLConnection也能夠這樣作 + (void)withGroup:(dispatch_group_t)group sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler { if (group == NULL) { [self sendAsynchronousRequest:request queue:queue completionHandler:handler]; } else { dispatch_group_enter(group); [self sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){ handler(response, data, error); dispatch_group_leave(group); }]; } }

注意事項

  • dispatch_group_async等價於dispatch_group_enter() 和 dispatch_group_leave()的組合。
  • dispatch_group_enter() 必須運行在 dispatch_group_leave() 以前。
  • dispatch_group_enter() 和 dispatch_group_leave() 須要成對出現的

Dispatch Block

隊列執行任務都是block的方式,

  • 建立block
- (void)createDispatchBlock {
    //normal way dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); dispatch_block_t block = dispatch_block_create(0, ^{ NSLog(@"run block"); }); dispatch_async(concurrentQueue, block); //QOS way dispatch_block_t qosBlock = dispatch_block_create_with_qos_class(0, QOS_CLASS_USER_INITIATED, -1, ^{ NSLog(@"run qos block"); }); dispatch_async(concurrentQueue, qosBlock); }
  • dispatch_block_wait:能夠根據dispatch block來設置等待時間,參數DISPATCH_TIME_FOREVER會一直等待block結束
- (void)dispatchBlockWaitDemo {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL); dispatch_block_t block = dispatch_block_create(0, ^{ NSLog(@"star"); [NSThread sleepForTimeInterval:5.f]; NSLog(@"end"); }); dispatch_async(serialQueue, block); //設置DISPATCH_TIME_FOREVER會一直等到前面任務都完成 dispatch_block_wait(block, DISPATCH_TIME_FOREVER); NSLog(@"ok, now can go on"); }
  • dispatch_block_notify:能夠監視指定dispatch block結束,而後再加入一個block到隊列中。三個參數分別爲,第一個是須要監視的block,第二個參數是須要提交執行的隊列,第三個是待加入到隊列中的block
- (void)dispatchBlockNotifyDemo {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL); dispatch_block_t firstBlock = dispatch_block_create(0, ^{ NSLog(@"first block start"); [NSThread sleepForTimeInterval:2.f]; NSLog(@"first block end"); }); dispatch_async(serialQueue, firstBlock); dispatch_block_t secondBlock = dispatch_block_create(0, ^{ NSLog(@"second block run"); }); //first block執行完纔在serial queue中執行second block dispatch_block_notify(firstBlock, serialQueue, secondBlock); }
  • dispatch_block_cancel:iOS8後GCD支持對dispatch block的取消
- (void)dispatchBlockCancelDemo {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL); dispatch_block_t firstBlock = dispatch_block_create(0, ^{ NSLog(@"first block start"); [NSThread sleepForTimeInterval:2.f]; NSLog(@"first block end"); }); dispatch_block_t secondBlock = dispatch_block_create(0, ^{ NSLog(@"second block run"); }); dispatch_async(serialQueue, firstBlock); dispatch_async(serialQueue, secondBlock); //取消secondBlock dispatch_block_cancel(secondBlock); }

使用dispatch block object(調度塊)在任務執行前進行取消

dispatch block object能夠爲隊列中的對象設置 示例,下載圖片中途進行取消

func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) { var storedError: NSError! let downloadGroup = dispatch_group_create() var addresses = [OverlyAttachedGirlfriendURLString, SuccessKidURLString, LotsOfFacesURLString] addresses += addresses + addresses // 擴展address數組,複製3份 var blocks: [dispatch_block_t] = [] // 一個保存block的數組 for i in 0 ..< addresses.count { dispatch_group_enter(downloadGroup) let block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) { // 建立一個block,block的標誌是DISPATCH_BLOCK_INHERIT_QOS_CLASS let index = Int(i) let address = addresses[index] let url = NSURL(string: address) let photo = DownloadPhoto(url: url!) { image, error in if let error = error { storedError = error } dispatch_group_leave(downloadGroup) } PhotoManager.sharedManager.addPhoto(photo) } blocks.append(block) dispatch_async(GlobalMainQueue, block) // 把這個block放到GlobalMainQueue上異步調用。由於全局隊列是一個順序隊列因此方便取消對象block,同時能夠保證下載任務在downloadPhotosWithCompletion返回後纔開始執行。 } for block in blocks[3 ..< blocks.count] { let cancel = arc4random_uniform(2) // 隨機返回一個整數,會返回0或1 if cancel == 1 { dispatch_block_cancel(block) // 若是是1就取消block,這個只能發生在block還在隊列中並無開始的狀況下。由於把block已經放到了GlobalMainQueue中,因此這個地方會先執行,執行完了纔會執行block。 dispatch_group_leave(downloadGroup) // 由於已經dispatch_group_enter了,因此取消時也要將其都leave掉。 } } dispatch_group_notify(downloadGroup, GlobalMainQueue) { if let completion = completion { completion(error: storedError) } } }

Dispatch IO 文件操做

dispatch io讀取文件的方式相似於下面的方式,多個線程去讀取文件的切片數據,對於大的數據文件這樣會比單線程要快不少。

dispatch_async(queue,^{/*read 0-99 bytes*/}); dispatch_async(queue,^{/*read 100-199 bytes*/}); dispatch_async(queue,^{/*read 200-299 bytes*/});
  • dispatch_io_create:建立dispatch io
  • dispatch_io_set_low_water:指定切割文件大小
  • dispatch_io_read:讀取切割的文件而後合併。

蘋果系統日誌API裏用到了這個技術,能夠在這裏查看:https://github.com/Apple-FOSS-Mirror/Libc/blob/2ca2ae74647714acfc18674c3114b1a5d3325d7d/gen/asl.c

pipe_q = dispatch_queue_create("PipeQ", NULL); //建立 pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){ close(fd); }); *out_fd = fdpair[1]; //設置切割大小 dispatch_io_set_low_water(pipe_channel, SIZE_MAX); dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){ if (err == 0) { size_t len = dispatch_data_get_size(pipedata); if (len > 0) { //對每次切塊數據的處理 const char *bytes = NULL; char *encoded; uint32_t eval; dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len); encoded = asl_core_encode_buffer(bytes, len); asl_msg_set_key_val(aux, ASL_KEY_AUX_DATA, encoded); free(encoded); eval = _asl_evaluate_send(NULL, (aslmsg)aux, -1); _asl_send_message(NULL, eval, aux, NULL); asl_msg_release(aux); dispatch_release(md); } } if (done) { //semaphore +1使得不須要再等待繼續執行下去。 dispatch_semaphore_signal(sem); dispatch_release(pipe_channel); dispatch_release(pipe_q); } }); 

Dispatch Source 用GCD監視進程

Dispatch Source用於監聽系統的底層對象,好比文件描述符,Mach端口,信號量等。主要處理的事件以下表

方法 說明
DISPATCH_SOURCE_TYPE_DATA_ADD 數據增長
DISPATCH_SOURCE_TYPE_DATA_OR 數據OR
DISPATCH_SOURCE_TYPE_MACH_SEND Mach端口發送
DISPATCH_SOURCE_TYPE_MACH_RECV Mach端口接收
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 內存狀況
DISPATCH_SOURCE_TYPE_PROC 進程事件
DISPATCH_SOURCE_TYPE_READ 讀數據
DISPATCH_SOURCE_TYPE_SIGNAL 信號
DISPATCH_SOURCE_TYPE_TIMER 定時器
DISPATCH_SOURCE_TYPE_VNODE 文件系統變化
DISPATCH_SOURCE_TYPE_WRITE 文件寫入

方法

  • dispatch_source_create:建立dispatch source,建立後會處於掛起狀態進行事件接收,須要設置事件處理handler進行事件處理。
  • dispatch_source_set_event_handler:設置事件處理handler
  • dispatch_source_set_cancel_handler:事件取消handler,就是在dispatch source釋放前作些清理的事。
  • dispatch_source_cancel:關閉dispatch source,設置的事件處理handler不會被執行,已經執行的事件handler不會取消。
NSRunningApplication *mail = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.mail"]; if (mail == nil) { return; } pid_t const pid = mail.processIdentifier; self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, DISPATCH_TARGET_QUEUE_DEFAULT); dispatch_source_set_event_handler(self.source, ^(){ NSLog(@"Mail quit."); }); //在事件源傳到你的事件處理前須要調用dispatch_resume()這個方法 dispatch_resume(self.source);

監視文件夾內文件變化

NSURL *directoryURL; // assume this is set to a directory int const fd = open([[directoryURL path] fileSystemRepresentation], O_EVTONLY); if (fd < 0) { char buffer[80]; strerror_r(errno, buffer, sizeof(buffer)); NSLog(@"Unable to open \"%@\": %s (%d)", [directoryURL path], buffer, errno); return; } dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE, DISPATCH_TARGET_QUEUE_DEFAULT); dispatch_source_set_event_handler(source, ^(){ unsigned long const data = dispatch_source_get_data(source); if (data & DISPATCH_VNODE_WRITE) { NSLog(@"The directory changed."); } if (data & DISPATCH_VNODE_DELETE) { NSLog(@"The directory has been deleted."); } }); dispatch_source_set_cancel_handler(source, ^(){ close(fd); }); self.source = source; dispatch_resume(self.source); //還要注意須要用DISPATCH_VNODE_DELETE 去檢查監視的文件或文件夾是否被刪除,若是刪除了就中止監聽

NSTimer在主線程的runloop裏會在runloop切換其它模式時中止,這時就須要手動在子線程開啓一個模式爲NSRunLoopCommonModes的runloop,若是不想開啓一個新的runloop能夠用不跟runloop關聯的dispatch source timer,以下。

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, DISPATCH_TARGET_QUEUE_DEFAULT); dispatch_source_set_event_handler(source, ^(){ NSLog(@"Time flies."); }); dispatch_time_t start dispatch_source_set_timer(source, DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC,100ull * NSEC_PER_MSEC); self.source = source; dispatch_resume(self.source);

Dispatch Semaphore和的介紹

另一種保證同步的方法。使用dispatch_semaphore_signal加1dispatch_semaphore_wait減1,爲0時等待的設置方式來達到線程同步的目的和同步鎖同樣可以解決資源搶佔的問題。

//dispatch semaphore
- (void)dispatchSemaphoreDemo { //建立semaphore dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"start"); [NSThread sleepForTimeInterval:1.f]; NSLog(@"semaphore +1"); dispatch_semaphore_signal(semaphore); //+1 semaphore }); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"continue"); }

這裏簡單介紹下iOS中經常使用的各類鎖和他們的性能。

  • NSRecursiveLock:遞歸鎖,能夠在一個線程中反覆獲取鎖不會形成死鎖,這個過程會記錄獲取鎖和釋放鎖的次數來達到什麼時候釋放的做用。
  • NSDistributedLock:分佈鎖,基於文件方式的鎖機制,能夠跨進程訪問。
  • NSConditionLock:條件鎖,用戶定義條件,確保一個線程能夠獲取知足必定條件的鎖。由於線程間競爭會涉及到條件鎖檢測,系統調用上下切換頻繁致使耗時是幾個鎖裏最長的。
  • OSSpinLock:自旋鎖,不進入內核,減小上下文切換,性能最高,但搶佔多時會佔用較多cpu,好點多,這時使用pthread_mutex較好。
  • pthread_mutex_t:同步鎖基於C語言,底層api性能高,使用方法和其它的相似。
  • @synchronized:更加簡單。

dispatch_suspend和dispatch_resume掛起和恢復隊列

dispatch_suspend這裏掛起不會暫停正在執行的block,只是可以暫停還沒執行的block。

dispatch_set_context和dispatch_get_context

GCD深刻操做

  • 緩衝區:dispatch_data_t基於零碎的內存區域,使用dispatch_data_apply來遍歷,還能夠用dispatch_data_create_subrange來建立一個不作任何拷貝的子區域
  • I/O調度:使用GCD提供的dispatch_io_read,dispatch_io_write和dispatch_io_close
  • 測試:使用dispatch_benchmark小工具
  • 原子操做: libkern/OSAtomic.h裏能夠查看那些函數,用於底層多線程編程。

GCD死鎖

當前串行隊列裏面同步執行當前串行隊列就會死鎖,解決的方法就是將同步的串行隊列放到另一個線程就可以解決。

- (void)deadLockCase1 {
    NSLog(@"1"); //主隊列的同步線程,按照FIFO的原則(先入先出),2排在3後面會等3執行完,但由於同步線程,3又要等2執行完,相互等待成爲死鎖。 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2"); }); NSLog(@"3"); } - (void)deadLockCase2 { NSLog(@"1"); //3會等2,由於2在全局並行隊列裏,不須要等待3,這樣2執行完回到主隊列,3就開始執行 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ NSLog(@"2"); }); NSLog(@"3"); } - (void)deadLockCase3 { dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL); NSLog(@"1"); dispatch_async(serialQueue, ^{ NSLog(@"2"); //串行隊列裏面同步一個串行隊列就會死鎖 dispatch_sync(serialQueue, ^{ NSLog(@"3"); }); NSLog(@"4"); }); NSLog(@"5"); } - (void)deadLockCase4 { NSLog(@"1"); dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"2"); //將同步的串行隊列放到另一個線程就可以解決 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"3"); }); NSLog(@"4"); }); NSLog(@"5"); } - (void)deadLockCase5 { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"1"); //回到主線程發現死循環後面就無法執行了 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2"); }); NSLog(@"3"); }); NSLog(@"4"); //死循環 while (1) { // } }

GCD實際使用

FMDB如何使用dispatch_queue_set_specific和dispatch_get_specific來防止死鎖

做用相似objc_setAssociatedObject跟objc_getAssociatedObject

static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey; //建立串行隊列,全部數據庫的操做都在這個隊列裏 _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); //標記隊列 dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL); //檢查是不是同一個隊列來避免死鎖的方法 - (void)inDatabase:(void (^)(FMDatabase *db))block { FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey); assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock"); }

iOS系統版本新特性

iOS8

iOS8新加了一個功能叫Quality of Service(QoS),裏面提供了一下幾個更容易理解的枚舉名來使用user interactive,user initiated,utility和background。下面的表作了對比

Global queue Corresponding QoS class 說明
Main thread NSQualityOfServiceUserInteractive UI相關,交互等
DISPATCH_QUEUE_PRIORITY_HIGH NSQualityOfServiceUserInitiated 用戶發起須要立刻獲得結果進行後續任務
DISPATCH_QUEUE_PRIORITY_DEFAULT NSQualityOfServiceDefault 默認的不該該使用這個設置任務
DISPATCH_QUEUE_PRIORITY_LOW NSQualityOfServiceUtility 花費時間稍多好比下載,須要幾秒或幾分鐘的
DISPATCH_QUEUE_PRIORITY_BACKGROUND NSQualityOfServiceBackground 不可見在後臺的操做可能須要好幾分鐘甚至幾小時的

參考資料

WWDC

文檔