亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

gcd 學習總結 (二)

2019-11-09 15:14:40
字體:
來源:轉載
供稿:網友

細說GCD(Grand Central Dispatch)如何用

獵戶座 edited this page on 22 Jul 2016 · 3 revisions

Pages 60

HomeAuto LayoutBlockCameraCFRunLoopCocoapodsCollection View動畫Core AnimationCore DataCore ImageGPU處理圖像iOS Background TasksiOS書籍推薦iOS函數響應式編程以及ReactiveCocoa的使用iOS基礎集合類Show 45 more pages…
Clone this wiki locally

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

GCD(Grand Central Dispatch) 介紹

GCD屬于系統級的線程管理,在Dispatch queue中執行需要執行的任務性能非常的高。GCD這塊已經開源,地址http://libdispatch.macosforge.org。GCD中的FIFO隊列稱為dispatch queue,用來保證先進來的任務先得到執行。

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 queuequeue = 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_classdispatch_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_queuedispatch_queue_t queue = dispatch_queue_create("com.starming.gcddemo.settargetqueue",NULL); //需要設置優先級的queuedispatch_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,隊列也可以是并行的,同一時間執行多個block

- (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等級表示用戶不會察覺的任務,使用它來處理預加載,或者不需要用戶交互和對時間不敏感的任務。

示例:后臺加載顯示圖片

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等級參數的寫法

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只有一份實例

+ (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 iodispatch_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_SENDMach端口發送
DISPATCH_SOURCE_TYPE_MACH_RECVMach端口接收
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:設置事件處理handlerdispatch_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 directoryint 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 startdispatch_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 queueCorresponding QoS class說明
Main threadNSQualityOfServiceUserInteractiveUI相關,交互等
DISPATCH_QUEUE_PRIORITY_HIGHNSQualityOfServiceUserInitiated用戶發起需要馬上得到結果進行后續任務
DISPATCH_QUEUE_PRIORITY_DEFAULTNSQualityOfServiceDefault默認的不應該使用這個設置任務
DISPATCH_QUEUE_PRIORITY_LOWNSQualityOfServiceUtility花費時間稍多比如下載,需要幾秒或幾分鐘的
DISPATCH_QUEUE_PRIORITY_BACKGROUNDNSQualityOfServiceBackground不可見在后臺的操作可能需要好幾分鐘甚至幾小時的

參考資料

WWDC

Building Responsive and Efficient Apps with GCD:https://developer.apple.com/videos/play/wwdc2015-718/

文檔

官方文檔:https://developer.apple.com/library/prerelease/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩精品日韩在线观看| 精品一区二区三区四区在线| www.久久久久久.com| 国产99视频精品免视看7| 亚洲欧美日本精品| 亚洲国产精品国自产拍av秋霞| 欧美xxxwww| 一道本无吗dⅴd在线播放一区| 亚洲最大av网| 在线观看国产精品日韩av| 伊人久久久久久久久久| 综合网日日天干夜夜久久| 欧美精品久久久久| 国产精品亚洲视频在线观看| 久久视频在线看| 久久不射热爱视频精品| 伊人成人开心激情综合网| 伊人久久久久久久久久久久久| 国产精品免费视频久久久| 欧美激情亚洲另类| 狠狠色狠狠色综合日日五| 成人精品视频久久久久| 91免费精品视频| 国产综合在线看| 在线观看欧美视频| 欧美在线视频导航| 久久久久久久91| 国产精品综合久久久| 国产91在线播放精品91| 日韩精品丝袜在线| 97精品伊人久久久大香线蕉| 国产精品美女av| 尤物精品国产第一福利三区| 国模吧一区二区三区| 久久久精品日本| 最新国产精品拍自在线播放| 97久久久免费福利网址| 亚洲精品电影久久久| 91精品国产91久久久久久最新| 最近2019中文免费高清视频观看www99| 欧美激情2020午夜免费观看| 国产精品揄拍一区二区| 亚洲va国产va天堂va久久| 成人福利网站在线观看| 色综合伊人色综合网| 国产脚交av在线一区二区| 国产精品96久久久久久又黄又硬| 亚洲精品av在线播放| 国产精品嫩草影院久久久| 91日本在线视频| 中文字幕日韩在线视频| 久久久影视精品| 欧美综合一区第一页| 亚洲成色777777女色窝| 97国产成人精品视频| 91精品国产777在线观看| 欧美激情综合亚洲一二区| 色噜噜狠狠狠综合曰曰曰88av| 免费91麻豆精品国产自产在线观看| 国产日韩欧美日韩| 狠狠躁夜夜躁久久躁别揉| 亚洲精品美女在线观看| 日韩有码在线播放| 欧美一级片免费在线| 欧美激情精品久久久久久蜜臀| 亚洲精品视频在线观看视频| 国产精品露脸av在线| 久久精品国产成人精品| 国产日韩精品在线播放| 国产精品精品视频一区二区三区| 97超级碰碰人国产在线观看| 国产99久久精品一区二区 夜夜躁日日躁| 91丝袜美腿美女视频网站| 中国china体内裑精亚洲片| 亚洲精品电影网在线观看| 日韩在线观看免费av| 丝袜美腿精品国产二区| 91精品在线播放| 欧美大尺度在线观看| 欧美激情一区二区三区成人| 日韩中文字幕免费看| 国产精品美女在线观看| 久久久伊人日本| 欧美精品一区二区三区国产精品| 蜜臀久久99精品久久久久久宅男| 国产精品福利无圣光在线一区| 海角国产乱辈乱精品视频| 国产成人91久久精品| 亚洲成年人影院在线| 欧美电影在线观看网站| www.日韩免费| 国产日韩一区在线| 日韩精品视频在线观看免费| 久久久久亚洲精品成人网小说| 国产亚洲欧美日韩精品| 色与欲影视天天看综合网| 九九热r在线视频精品| 亚洲美女自拍视频| 亚洲一区亚洲二区亚洲三区| 精品国产1区2区| 欧美国产视频一区二区| 久久久久久18| 国产成人精品国内自产拍免费看| 国产精品jvid在线观看蜜臀| 成人免费福利视频| 欧美小视频在线| 992tv成人免费影院| 中文综合在线观看| 日本精品久久电影| 精品国产一区二区三区久久| 欧美日本高清视频| 国产精品久久久久不卡| 欧美激情伊人电影| 亚洲男女自偷自拍图片另类| 亚洲国产日韩一区| 欧美丰满少妇xxxxx做受| 中文字幕亚洲色图| 国产精品亚洲美女av网站| 国产精品流白浆视频| 伊人久久综合97精品| 成人久久久久久久| 欧美尤物巨大精品爽| 国产精品久久久久一区二区| 欧美影院在线播放| 国产成人精品在线| 久久精品电影网| 国产伦精品一区二区三区精品视频| 成人福利在线观看| 久久久久久久久久久国产| 精品欧美激情精品一区| 久久久久久有精品国产| 欧美视频一二三| 狠狠躁夜夜躁人人躁婷婷91| 欧美最猛性xxxxx(亚洲精品)| 91av在线免费观看视频| 亚洲国产日韩精品在线| 97在线免费观看| 九九热这里只有精品免费看| 午夜精品久久久久久久99黑人| 日本欧美国产在线| 国产91色在线| 国产成人av在线| 啪一啪鲁一鲁2019在线视频| 亚洲色在线视频| 亚洲午夜未满十八勿入免费观看全集| 中文字幕av一区二区| 亚洲美女性视频| 欧美成在线视频| 亚洲精品mp4| www.久久久久| 日韩久久精品电影| 精品久久久在线观看| 午夜精品久久久久久久久久久久久| 欧洲成人午夜免费大片| 亚洲男人天堂九九视频| 欧美性20hd另类| 精品国产一区二区在线| 久久精品国产成人精品| 日韩av片免费在线观看| 日韩免费看的电影电视剧大全| 国产精品xxx视频| 色与欲影视天天看综合网| 国产在线观看精品一区二区三区| 精品成人久久av|