1. 程式人生 > >Grand Central Dispatch(GCD)

Grand Central Dispatch(GCD)

 GCD      GCD是非同步執行任務的技術之一。 GCD使用很簡潔的記述方法,實現了極為複雜繁瑣的多執行緒程式設計。 dispatch_async(queue, ^{      dispatch_async( dispatch_get_main_queue(), ^{      //只在主執行緒可以執行的處理      //例如使用者介面更新      }); }); 在NSObject中,提供了兩個例項方法來實現簡單的多執行緒技術:performSelectorInBackground:withObject    performSelectorOnMainThread。  我們也可以改用performSelector系方法來實現前面使用的GCD。 //NSObject
  performSelectorInBackground:withObject:方法中執行後臺執行緒 - (void)launchThreadByNSObject_performSelectorInBackground_withObject {      [self performSelectorInBackground:@selector(doWork) withObject:nil]; } - (void)doWork {      @autoreleasepool{           //長時間處理,   例如AR用畫像識別    例如資料庫訪問           [self performSelectorOnMainThread:@selector(doneWork) withObject:nil waitUntilDone:NO];      } } - (void)doneWork {      //
例如使用者介面更新 } performSelector系方法確實比使用NSThread簡單,但是相比GCD,卻有所不及。 多執行緒程式設計 上下文切換:cpu的暫存器等資訊儲存到格子路徑專用的記憶體塊中,從切換目標路徑專用的記憶體塊中復原cpu暫存器等資訊,繼續執行切換路徑的cpu命令。 使用多執行緒的程式可以在某個執行緒和其他執行緒之間反覆多次進行上下文切換。      應用程式啟動的時候,通過最先執行的執行緒,即:主執行緒 來描繪使用者介面、處理觸控式螢幕幕的事件等。   如果在該主執行緒中進行長時間的處理,就會妨礙主執行緒的執行(阻塞);  在os x和ios中,會妨礙主執行緒中被稱為RunLoop的主迴圈的執行,從而導致不能更新使用者介面,應用程式的畫面長時間停滯等問題。 如圖:

但是當我們使用多執行緒,在執行長時間的處理時仍可以保證使用者介面的響應效能。 GCD的API 1、Dispatch Queue      apple:開發者要做的只是定義想執行的任務,並追加到適當的Dispatch Queue中。 dispatch_async(queue, ^{      //想知性的任務 }); 這樣就可以使指定的Block在另一執行緒中執行了。 來看一下Dispatch Queue是什麼:  執行處理的等待佇列。  我們通過dispatch_async函式等API, 在Block語法中記述想要執行的處理,並將其追加到Dispatch Queue中。  而Dispatch Queue存在兩種佇列1、等待 Serial Dispatch Queue   2、不等待  Concurrent Dispatch Queue   如圖:
不等待的情況下,可以並行執行多個處理,但並行執行的處理數量取決於當前系統的狀態:IOS和OS X 基於Dispatch Queue中的處理數,CPU核數以及CPU符合等當前系統的狀態來決定Concurrent Dispatch Queue中並行執行的處理數。 並行執行:使用多個執行緒來同時執行多個處理。  如圖:
2、dispatch_queue_create      通過此函式,可生成Dispatch Queue 如: dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL); 其實我們可以生成多個Dispatch Queue,  當生成多個Serial Dispatch Queue時,各個Serial Dispatch Queue將並行執行,  雖然在1個Serial Dispatch Queue中 同事只能執行一個追加處理,但是如果將處理分別追加到4個Serial Dispatch Queue中,各個Serial Dispatch Queue執行一個, 也就是4個同時處理, 如圖:
我們如果過多的使用多執行緒,就會消耗大量的記憶體,引起大量的上下文切換,導致系統性能大幅度的降低。 但是要注意的是不能多個執行緒同時更新相同的資料, 如圖:
如果想要並行執行,不發生資料競爭的問題,使用Concurrent Dispatch Queue。  對於他來說,不管生成多少,由於XNU核心只使用有效管理的執行緒,因此不會發生Serial Dispatch Queue的問題。
     在dispatch_queue_create函式中,第一個引數指定Serial Dispatch Queue的名稱。 Dispatch Queue的名稱推薦使用應用程式ID 這種逆序全程域名(FQDN, fully qualified domain name);  他在xcode和Instruments的除錯其中作為Dispatch Queue名稱表示。   並且,這個名稱也會出現在應用程式崩潰時所產生的CrashLog中。 命名是應遵循:對我們程式設計人員來說簡單易懂,對使用者來說也要易懂。也可以為NULL,但是你除錯中一個不會希望設定為NULL。      Serial Dispatch Queue:dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);      Concurrent Dispatch Queue:dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", DISPATCH_QUEUE_CONCURRENT);      他的返回值是dispatch_queue_t型別。 dispatch_queue_tmyConcurrentDispatchQueue =dispatch_queue_create("com.example.gcd.MyConcurrentDispatchQueue",DISPATCH_QUEUE_CONCURRENT);
       
dispatch_async(myConcurrentDispatchQueue, ^{NSLog(@"block on MyConcurrentDispatchQueue");});      這樣,程式碼在Concurrent Dispatch Queue中執行指定的Block。 這裡要注意的是,即使有arc,我們也需要去手動釋放Dispatch Queue。 因為他沒有像Block那樣具有作為oc物件來處理的技術。      dispatch_release函式來釋放。      dispatch_release(mySerialDispatchQueue);      有了release,也會對應的有retain了。      dispatch_async函式中追加Block到Dispatch Queue後,即使立即釋放Dispatch Queue,該Dispatch Queue由於被Block持有,所以不會被廢棄。所以Block可以執行。 Block執行結束後會釋放Dispatch Queue,這時誰都不持有Dispatch Queue,所以他會被廢棄。 3、Main Dispatch Queue/Global Dispatch Queue      獲取Dispatch Queue      追加到Main Dispatch Queue的處理在主執行緒的RunLoop中執行, 由於在主執行緒中執行,因此要將使用者介面的介面更新等一些必須在主執行緒中執行的處理追加到Main Dispatch Queue中使用。      這與NSObject的performSelectorOnMainThread例項方法這一執行方法相同。 如圖:
另一個Global Dispatch Queue是所有應用程式都能夠使用的Concurrent Dispatch Queue,沒有必要通過dispatch_queue_create函式逐個生成Concurrent Dispatch Queue。只要獲取Global Dispatch Queue使用就可以了。      Global Dispatch Queue有四個優先順序:High Priority, Default Priority, Low Priority, Background Priority 。 (高,預設,低,後臺)   通過XNU核心管理的用於Global Dispatch Queue執行緒, 將各自使用的Global Dispatch Queue的執行優先順序作為執行緒的執行優先順序使用。   向Global Dispatch Queue追加處理時,應選擇與處理內容對應的執行優先順序的Global Dispatch Queue。      看一下Dispatch Queue的種類: 獲取方法:      //Main Dispatch Queue的獲取方法 dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();      //Global Dispatch Queue高優先順序的獲取方法 dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);      //Global Dispatch Queue 預設優先順序的獲取方法 dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);      //Global Dispatch Queue 低優先順序的獲取方法 dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);      //Global Dispatch Queue 後臺優先順序的獲取方法 dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); 使用: //預設優先順序的Global Dispatch Queue中執行Block dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{      //可並行執行的處理      //在Main Dispatch Queue中執行Block      dispatch_async(dispatch_get_main_queue(), ^{           //只能在主執行緒中執行的處理           });      }); 4、dispatch_set_target_queue      他生成的Dispatch Queue都是預設優先順序Global Dispatch Queue相同執行優先順序的執行緒。 變更生成的執行優先順序也需要使用他。 dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create(“com.example.***”, NULL); dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);      如果你在多個Serial Dispatch Queue中用dispatch_set_target_queue函式指定目標為某一個Serial Dispatch Queue, 那麼原先本應並行執行的多個Serial Dispatch Queue,在目標Serial Dispatch Queue上只能同時執行一個處理:
     在必須將不可並行執行的處理追加到多個Serial Dispatch Queue中時, 如果使用dispatch_set_target_queue函式將目標指定為某一個Serial Dispatch Queue, 就可以防止處理並行執行。 5、dispatch_after      延遲處理。  3秒如下: dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC); dispatch_after(time, dispatch_get_main_queue(), ^{      NSLog(@“waited at least three seconds.");      }); dispatch_after是在指定時間追加到Dispatch Queue中。 6、Dispatch Group      監視Dispatch Queue處理執行的結束。 dispatch_group_create(); dispatch_group_async(); dispatch_group_notify(); dispatch_group_wait(); 7、dispatch_barrier_async      訪問資料庫或者檔案的時候,使用Serial Dispatch Queue可以避免資料競爭的問題。      為了效率,讀取處理可以追加到Concurrent Dispatch Queue中,寫入處理在任一個讀取處理沒有執行的狀態下,追加到Serial Dispatch Queue中。也就是說,在寫入處理結束之前,讀取處理不可執行。      dispatch_barrier_async會等待追加到Concurrent DIspatch Queue上的並行執行的處理全部結束之後,再將指定的處理追加到該Queue中。然後再由dispatch_barrier_async函式追加的處理執行完畢之後,Concurrent Dispatch Queue才恢復為一般的動作,追加到該Queue的處理又開始並行執行。 如圖:
8、dispatch_sync      async:非同步,將指定的Block 非同步地追加到指定的Dispatch Queue中, dispatch_async函式不做任何等待,如圖:      sync:同步,將指定的Block 同步 追加到指定的Dispatch Queue中, 在追加Block結束之前,dispatch_sync會一直等待, 如圖: 如:執行Main Dispatch Queue時,使用另外的執行緒Global Dispatch Queue進行處理,處理結束後立即使用所得到的結果,  這時要用sync。
sync在指定的處理執行結束之前,函式是不會返回的。
死鎖: dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_sync(queue, ^{NSLog(@“Hello");}); 此時,在主執行緒中執行指定的Block,並等待其執行結束,但是主執行緒中正在執行這些原始碼,所以不能追加到Main Dispatch Queue的Block。 dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_async(queue, ^{      dispatch_sync(queue, ^{NSLog(@“hello");});      }); 此時,Main Dispatch Queue中執行的Block等待Main Dispatch Queue中要執行的Block執行結束。  死鎖了。 9、dispatch_apply      他是dispatch_sync函式和Dispatch Group的關聯API。此函式按指定的次數將指定的Block追加到指定的Dispatch Queue中,並等待全部處理之行結束。 dispatch_apply(10, queue, ^(size_t index){NSLog(@“dsf");}); 如果用在陣列中,那麼我們就不用編寫for迴圈了。 直接[array count] 由於dispatch_apply函式與dispatch_sync函式相同,會等待處理執行結束,因此推薦在dispatch_async函式中非同步地執行dispatch_apply函式。 10、dispatch_suspend/dispatch_resume      當追加大量處理到Dispatch Queue時, 在追加處理的過程中,有時希望不執行已追加的處理。  此時,我們需要掛起Dispatch Queue。當可以執行時再恢復。 suspend是掛起。 resume是恢復。 11、Dispatch Semaphore      更新資料的時候,會產生資料不一致的情況,有時候應用程式還會異常結束。    雖然使用Serial Dispatch Queue和dispatch_barrier_async函式可避免這類問題,但是有必要進行更加細粒度的排他控制。      Dispatch Semaphore是持有計數的訊號。  計數0時,等待。計數1或者大於1時,減去1而不等待。 dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);   引數表示計數的初始值,此時為1 。  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);      此函式等待Dispatch Semaphore的計數值達到大於或等於1.  當計數大於1或者再等待中計數值大於等於1,對該計數進行減法並從dispatch_semaphore_wait函式返回。 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //生成Dispatch Semaphore      Dispatch Semaphore的計數初始值設定為1      保證可訪問NSMutableArray類物件的執行緒 同時只能有1個 dispatch_semaphore_t semahpore = dispatch_semaphore_create(1); NSMutbaleArray *array = [[NSMutableArray alloc] init]; for (int i = 0; i < 100000; ++i) {      dispatch_async(queue, ^{           //等待Dispatch Semaphore   一直等待,知道Dispatch Semaphore 的計數值達到大於等於1.                dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);           //由於dispatch Semaphore的計數值達到大於等於1,  所以將Dispatch Semaphore 的計數值減去1           //dispatch_semaphore_wait函式執行返回。           //執行到此時的Dispatch Semaphore的計數值恆為0.         由於可訪問NSMutableArray累物件的執行緒只有一個,因此可安全的進行更新                [array addObject:[NSNumber numberWithInt:i];           //排他控制處理結束, 所以通過dispatch_semaphore_signal函式,將Dispatch Semaphore的計數值加1.           //如果有通過dispatch_semaphore_wait函式等待Dispatch Semaphore的計數值增加的執行緒,就由最先等待的執行緒執行。                dispatch_semaphore_signal(semaphore);           }); } //如果使用結束,需要以下這樣 釋放Dispatch Semaphore:  dispatch_release(semaphore); 12、dispatch_once      保證應用程式執行中只執行一次指定處理的api。      這裡就是單例模式,在生成單例物件時使用。 13、Dispatch I/O      一次使用多執行緒更快地並列讀取檔案。      通過Dispatch I/O讀寫檔案時,使用Global Dispatch Queue將一個檔案按某個大小read/write。      也可以將檔案分割為一塊一塊地進行讀取處理,分割讀取的資料通過使用Dispatch Data可以更為簡單地進行結合和分割 。      dispatch_io_create  生成Dispatch IO, 指定發生錯誤時用來執行處理的Block,以及執行該Block的Dispatch Queue。      dispatch_io_set_low_water函式 設定一次讀取的大小(分割的大小),      dispatch_io_read函式使用Global Dispatch Queue開始並列讀取。                                                                                                            -----------2014/3/24  Beijing