GCD理解
GCD
Dispatch Queue
執行處理的等待隊列。應用程序編程人員通過dispatch_async函數等API,在Block語法中記述想執行的處理並將其追加到Dispatch Queue中,Dispatch Queue按照追加的順序(先進先出FIFO)執行處理。
串行隊列(Serial Dispatch Queue)
因爲要等待現在執行中的處理結束,所以首先執行blk0,blk0執行結束後,接着執行blk1,blk1結束後再開始執行blk2,如此重複。同時執行的處理數只能有1個。
只在爲了避免多線程編程問題之一——多個線程更新相同資源導致數據競爭時使用Serial Dispatch Queue
Serial Dispatch Queue的生成個數應當僅限所必需的數量,例如更新數據庫時一個表生成一個Serial Dispatch Queue,更新文件時一個文件或是可以分割的1個文件塊生成1個Serial Dispatch Queue。絕不能激動之下大量生成Serial Dispatch Queue
併發隊列(Concurrent Dispatch Queue)
因爲不用等待現在執行中的處理結束,所以首先執行blk0,不管blk0的執行是否結束,都開始執行後面的blk1,不管blk1的執行是否結束,都開始執行後面的blk2,如此重複循環
這樣雖然不用等待處理結束,可以並行執行多個處理,但並行執行的處理數量取決於當前系統的狀態。即iOS和OS X基於Dispatch Queue中的處理數,CPU核數以及CPU負荷等當前系統的狀態來決定併發隊列中並行執行的處理數
iOS和OS X的核心——XNU內核決定應當使用的線程數,並只生成所需的線程執行處理。另外,當處理結束,應當執行的處理數減少時,XNU內核會結束不再需要的線程。
假設準備4個Concurrent Dispatch Queue用線程,首先blk0再線程0中開始執行,接着blk1在線程1中、blk2在線程2中、blk3在線程3中開始執行。線程0中blk0執行結束後開始執行blk4,由於線程1中blk1的執行沒有結束,因此線程2中blk2執行結束後開始執行blk5,就這樣循環往復
dispatch_queue_create
dispatch_queue_create可生成Dispatch Queue,雖然Serial Dispatch Queue和Concurrent Dispatch Queue受到系統資源的限制,但用dispatch_queue_create函數可生成任意多個Dispatch Queue
當生成多個Serial Dispatch Queue時,各個Serial Dispatch Queue將並行執行。雖然在1個Serial Dispatch Queue中同時只能執行一個追加處理,但如果將處理分別追加到4個Serial Dispatch Queue中,各個Serial Dispatch Queue執行1個,即爲同時執行4個處理。
對於Concurrent Dispatch Queue來說,不管生成多少,由於XNU內核只使用有效管理的線程,因此不會發生Serial Dispatch Queue的那些問題
在iOS6.0以下通過dispatch_queue_create生成的Dispatch Queue在使用結束後通過dispatch_release函數釋放,iOS6.0以及以上ARC會自動管理
系統提供的Dispatch Queue
Main Dispatch Queue是在主線程執行的Dispatch Queue,因爲主線程只有一個,所以Main Dispatch Queue自然就是Serial Dispatch Queue,追加到Main Dispatch Queue的處理在主線程的RunLoop中執行。
Global Dispatch Queue有四個執行優先級,4個全局隊列,分別是高優先級(High Priority)、默認優先級(Default Priority)、低優先級(Low Priority)和後臺優先級(Background Priority)。通過XNU內核管理的用於Global Dispatch Queue的線程,將各自使用的Global Dispatch Queue的執行優先級作爲線程的執行優先級使用。
爲創建的Dispatch Queue創建優先級
自己創建的Dispatch Queue優先級都使用與默認優先級Global Dispatch Queue相同執行優先級的線程。而變更自己創建的Dispatch Queue執行優先級要使用dispatch_set_target_queue函數
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue",NULL);
dispatch_queue_ t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);
iOS 8之後建議用dispatch_queue_attr_t設置優先級
//iOS 8以上
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
dispatch_queue_t queues = dispatch_queue_create("com.yh.render", attr);
延遲執行(dispatch_after)
想在指定時間後執行處理的情況,可使用dispatch_after函數來實現
需要注意的是, dispatch_after函數並不是在指定時間後執行處理,而只是在指定時間追加處理到Dispatch Queue。此源代碼與在3秒後用dispatch_async函數追加Block到Main Dispatch Queue的相同。
因爲Main Dispatch Queue在主線程的RunLoop中執行,所以在比如每隔1/60秒執行的RunLoop中,Block最快在3秒後執行,最慢在3秒+1/60秒後執行,並且在Main Dispatch Queue有大量處理追加或主線程的處理本身有延遲時,這個時間會更長
// 從第一個參數中指定的時間開始,到第二個參數指定的毫微秒單位時間後的時間
// ull 是C語言的數值字面量,是顯式表明類型時使用的字符串("unsigned long long")
// dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);
// 第一個參數是指定時間用的dispatch_time_t類型值,該值使用dispatch_time函數或dispatch_walltime函數作成
// dispatch_walltime用於計算絕對時間
NSTimeInterval interval = [[NSDate date] timeIntervalSince1970];
double second;
double subsecond = modf(interval, &second);
struct timespec time;
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
dispatch_time_t miletone = dispatch_walltime(&time, 0);
dispatch_after(miletone, dispatch_get_main_queue(), ^{
NSLog(@"waited at least three seconds");
});
Dispatch Group
1. 在追加到Dispatch Queue中的多個處理全部結束後想執行結束處理。無論向什麼樣的Dispatch Queue中追加處理,使用Dispatch Group都可監視這些處理執行的結束。一旦檢測到所有處理執行結束,就可將結束的處理追加到Dispatch Queue中
在追加到Dispatch Group中的處理全部執行結束時,該源代碼中使用的dispatch_group_notify函數會將執行的Block追加到Dispatch Queue中,將第一個參數指定爲要監視的Dispatch Group。在追加到該Dispatch Group的全部處理執行結束時,將第三個參數的Block追加到第二個參數的Dispatch Queue中。
2. 也可以使用dispatch_group_wait函數僅等待全部處理執行結束
dispatch_group_wait函數的第二個參數指定爲等待的時間(超時)。它屬於dispatch_time_t類型的值。一旦調用dispatch_group_wait函數,該函數就處於調用的狀態而不返回。即執行dispatch_group_wait函數的現在的線程(當前線程)停止。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
sleep(2);
NSLog(@"blk0");
});
dispatch_group_async(group, queue, ^{
sleep(2);
NSLog(@"blk1");
});
dispatch_group_async(group, queue, ^{
sleep(6);
NSLog(@"blk2");
});
// 不會阻礙主線程
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"done");
});
// 會讓dispatch_group_wait的當前線程停止(這裏是寫在主線程上);
// long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// if (result == 0) {
// NSLog(@"done");
// }
dispatch_group_enter、dispatch_group_leave
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"第一個走完了");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"第二個走完了");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有任務完成,可以更新UI");
});
Dispatch Group的本質是一個初始value爲LONG_MAX的semaphore,通過信號量來實現一組任務的管理
dispatch_group_enter的邏輯是將dispatch_group_t轉換成dispatch_semaphore_t後將dsema_value的值減一。
dispatch_group_leave的邏輯是將dispatch_group_t轉換成dispatch_semaphore_t後將dsema_value的值加一。
當調用了dispatch_group_enter而沒有調用dispatch_group_leave時,會造成value值不等於LONG_MAX而不會走到喚醒邏輯,dispatch_group_notify函數的block無法執行或者dispatch_group_wait收不到semaphore_signal信號而卡住線程。
當dispatch_group_leave比dispatch_group_enter多調用了一次時,dispatch_semaphore_t的value會等於LONGMAX+1(2147483647+1),即long的負數最小值LONG_MIN(–2147483648)。因爲此時value小於0,所以會出現"Unbalanced call to dispatch_group_leave()"的崩潰
dispatch_barrier_async
多讀單寫問題:爲了高效率地進行訪問,讀取處理可以並行執行,寫入處理不可以與其他寫入處理以及包含讀取處理並行執行
dispatch_barrier_async,該函數同dispatch_queue_create函數生成的Concurrent Dispatch Queue一起使用(全局隊列使用無效)
dispatch_barrier_async函數會等待追加到Concurrent Dispatch Queue上的並行執行的處理全部結束之後,再將指定的處理追加到該Concurrent Dispatch Queue中。然後再由dispatch_barrier_async函數追加的處理執行完畢後,Concurrent Dispatch Queue才恢復爲一般的動作,追加到該Concurrent Dispatch Queue的處理又開始並行執行。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"blk0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk2");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"donw");
});
NSLog(@"dispatch_group_wait 結束");
});
dispatch_sync
dispatch_async:不做任何等待,將指定的Block"非同步"地追加到指定的Dispatch Queue中
dispatch_sync: 等待處理執行結束, 將指定的Block"同步"地追加到指定的Dispatch Queue中,如dispatch_group_wait說明:"等待"意味着當前線程停止
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
NSLog(@"test --- sync start");
sleep(5);
NSLog(@"test --- sync end");
});
NSLog(@"哈哈哈哈哈哈哈");
// test --- sync start test --- sync end 哈哈哈哈哈哈哈
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"test --- sync start");
sleep(5);
NSLog(@"test --- sync end");
});
NSLog(@"哈哈哈哈哈哈哈");
// test --- sync start 哈哈哈哈哈哈哈 test --- sync end
使用場景:
執行Main Dispatch Queue時,使用另外的線程Global Dispatch Queue進行處理,處理結束後立即使用所得到的結果。在這種情況下就要使用dispatch_sync函數
dispatch_sync使用簡單,也容易引起死鎖
如在主線程執行以下源代碼就會死鎖: 該源代碼在主線程執行的Block,並等待其執行結束,而其實在主線程正在執行這些源代碼,所以無法執行追加到主線程的Block
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{NSLog(@"Hello?")});
dispatch_apply
dispatch_apply是dispatch_sync和Dispatch Queue的關聯API,該函數按指定的次數將指定的Block追加到指定的Dispatch Queue中,並等待全部處理執行結束
輸出結果中最後的done必定在最後的位置上。這是因爲dispatch_apply函數會等待全部處理執行結束。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu",index);
});
NSLog(@"done");
// 推薦用法
dispatch_asynce(queue, ^{
dispatch_apply(10, queue,....
});
由於dispatch_apply函數會等待全部處理執行結束,所以推薦在dispatch_async函數中"非同步"地執行dispatch_apply函數
dispatch_suspend/dispatch_resume
dispatch_suspend: 掛起指定的dispatch Queue
dispatch_resume: 恢復指定的dispatch queue
掛起後,追加到dispatch queue中但尚未執行的處理在此之後停止執行,而恢復則使得這些處理能夠繼續執行。
Dispatch Semaphore
Dispatch Semaphore是持有計數的信號
dispatch_semaphore_wait函數等待Dispatch Semaphore的計數值達到大於或等於1
dispatch_semaphore_signal將Dispatch Semaphore的計數值+1
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"test --- async start");
sleep(5);
NSLog(@"test --- async end");
dispatch_semaphore_signal(semaphore);
});
// 會讓當前線程停止
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"111test --- async start");
sleep(5);
NSLog(@"111test --- async end");
dispatch_semaphore_signal(semaphore);
});
dispatch_once
dispatch I/O
如想提高文件讀取速度,可以嘗試使用Dispatch I/O