前文回顧: 上篇博客講到GCD的實現是由隊列和任務兩部分組成,其中獲取隊列的方式有兩種,第一種是通過GCD的API的dispatch_queue_create函數生成Dispatch Queue;第二種是直接使用系統提供的標準Dispatch Queue :Main Dispatch Queue和G ...
前文回顧:
上篇博客講到GCD的實現是由隊列和任務兩部分組成,其中獲取隊列的方式有兩種,第一種是通過GCD的API的dispatch_queue_create函數生成Dispatch Queue;第二種是直接使用系統提供的標準Dispatch Queue :Main Dispatch Queue和Global Dispatch Queue,具體的實現方式請參照上篇博客《iOS多線程開發之GCD(上篇)》。
這篇博客主要講解以下蘋果提供的一些常用GCD和代碼示例及其註意點。
- dispatch_set_target_queue
- dispatch_after
- dispatch_once / dispatch_apply
- Dispatch Group
- dispatch_barrier_sync
- dispatch_suspend / dispatch_resume
- Dispatch Semaphore
一、dispatch_set_target_queue
dispatch_set_target_queue中涉及的代碼示例來源於網路
1、dispatch_set_target_queue 更改Dispatch Queue的執行優先順序
dispatch_queue_create函數生成的DisPatch Queue不管是Serial DisPatch Queue還是Concurrent Dispatch Queue,執行的優先順序都與預設優先順序的Global Dispatch queue相同,如果需要變更生成的Dispatch Queue的執行優先順序則需要使用dispatch_set_target_queue函數。
dispatch_queue_t serialQueue = dispatch_queue_create("com.beckwang.www",NULL); dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0); // 第一個參數為要變更優先順序的queue,第二個參數是參照物,既將第一個queue的優先順序和第二個queue的優先順序設置一樣。 dispatch_set_target_queue(serialQueue, globalQueue);
2、dispatch_set_target_queue作為執行階層,修改隊列的目標隊列使多個serial queue在目標queue上一次只有一個執行
第一種情況:使用dispatch_set_target_queue(Dispatch Queue1, Dispatch Queue2)實現隊列的動態調度管理- (void)testTargetQueue2 { //創建一個串列隊列queue1 dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL); //創建一個串列隊列queue2 dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL); //使用dispatch_set_target_queue()實現隊列的動態調度管理 dispatch_set_target_queue(queue1, queue2); /* 那麼dispatchA上還未運行的block會在dispatchB上運行。這時如果暫停dispatchA運行: dispatch_suspend(dispatchA); 這時則只會暫停dispatchA上原來的block的執行,dispatchB的block則不受影響。而如果暫停dispatchB的運行,則會暫停dispatchA的運行。 dispatch隊列不支持cancel(取消),沒有實現dispatch_cancel()函數,不像NSOperationQueue,不得不說這是個小小的缺憾 */ dispatch_async(queue1, ^{ for (NSInteger i = 0; i < 10; i++) { NSLog(@"queue1:%@, %ld", [NSThread currentThread], i); [NSThread sleepForTimeInterval:0.5]; if (i == 5) { dispatch_suspend(queue2); } } }); dispatch_async(queue1, ^{ for (NSInteger i = 0; i < 100; i++) { NSLog(@"queue1:%@, %ld", [NSThread currentThread], i); } }); dispatch_async(queue2, ^{ for (NSInteger i = 0; i < 100; i++) { NSLog(@"queue2:%@, %ld", [NSThread currentThread], i); } }); }View Code 第二種情況:使用dispatch_set_target_queue將多個串列的queue指定到了同一目標,那麼著多個串列queue在目標queue上就是一次只能執行一個(化並行為串列)。
適用場景:一般都是把一個任務放到一個串列的queue中,如果這個任務被拆分了,被放置到多個串列的queue中,但實際還是需要這個任務同步執行,那麼就會有問題,因為多個串列queue之間是並行的。這時候dispatch_set_target_queue將起到作用。
(1)沒有使用dispatch_set_target_queue時:
- (void)testTargetQueue3 { //1.創建目標隊列 //dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL); //2.創建3個串列隊列 dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL); //3.將3個串列隊列分別添加到目標隊列 //dispatch_set_target_queue(queue1, targetQueue); //dispatch_set_target_queue(queue2, targetQueue); //dispatch_set_target_queue(queue3, targetQueue); dispatch_async(queue1, ^{ NSLog(@"1 in"); [NSThread sleepForTimeInterval:3.f]; NSLog(@"1 out"); }); dispatch_async(queue2, ^{ NSLog(@"2 in"); [NSThread sleepForTimeInterval:2.f]; NSLog(@"2 out"); }); dispatch_async(queue3, ^{ NSLog(@"3 in"); [NSThread sleepForTimeInterval:1.f]; NSLog(@"3 out"); }); }
列印結果:
2017-07-04 19:52:51.915 Test[5759:927698] 1 in 2017-07-04 19:52:51.915 Test[5759:927699] 2 in 2017-07-04 19:52:51.915 Test[5759:927701] 3 in 2017-07-04 19:52:52.916 Test[5759:927701] 3 out 2017-07-04 19:52:53.921 Test[5759:927699] 2 out 2017-07-04 19:52:54.919 Test[5759:927698] 1 out
結論:多個串列queue之間是並行的!
(2)使用dispatch_set_target_queue設置target
- (void)testTargetQueue3 { //1.創建目標隊列 dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL); //2.創建3個串列隊列 dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL); //3.將3個串列隊列分別添加到目標隊列 dispatch_set_target_queue(queue1, targetQueue); dispatch_set_target_queue(queue2, targetQueue); dispatch_set_target_queue(queue3, targetQueue); dispatch_async(queue1, ^{ NSLog(@"1 in"); [NSThread sleepForTimeInterval:3.f]; NSLog(@"1 out"); }); dispatch_async(queue2, ^{ NSLog(@"2 in"); [NSThread sleepForTimeInterval:2.f]; NSLog(@"2 out"); }); dispatch_async(queue3, ^{ NSLog(@"3 in"); [NSThread sleepForTimeInterval:1.f]; NSLog(@"3 out"); }); }
列印結果:
2017-07-04 19:58:33.667 Test[5830:968024] 1 in 2017-07-04 19:58:36.672 Test[5830:968024] 1 out 2017-07-04 19:58:36.672 Test[5830:968024] 2 in 2017-07-04 19:58:38.678 Test[5830:968024] 2 out 2017-07-04 19:58:38.679 Test[5830:968024] 3 in 2017-07-04 19:58:39.683 Test[5830:968024] 3 out
結論:多個串列queue之間是串列的!
二、dispatch_after
如果需要延時處理某件事情,我們可以使用dispatch_after,需要註意的是dispatch_after並不是將任務追加到隊列dispatch_queue後再根據時間參數延遲執行block代碼,而是在指定時間後追加任務到到dispatch_queue。代碼示例:
dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC); dispatch_after(time, dispatch_get_main_queue(), ^{ NSLog(@"這裡是dispatch_after測試"); });
// dispatch_get_main_queue ---> diapatch_get_gloab_queue 就可以更改執行線程
實現延時處理除了上面的GCD(dispatch_after)外,還可以通過以下方法:
(1)performSelector(NSObject)方法:
// 不帶參數 [self performSelector:@selector(doSomething) withObject:self afterDelay:3.0f]; // 帶參數 [self performSelector:@selector(delayDo:) withObject:@"paramtest" afterDelay:3.0f]; // 取消全部 [NSObject cancelPreviousPerformRequestsWithTarget:self]; // 取消不傳參的方法 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(delayDo:) object:nil]; // 取消傳參的方法 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(delayDo:) object:@"paramtest"];
(2)NSTimer的類方法:
// 不帶參數 [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(doSomething) userInfo:nil repeats:NO]; // 帶參數 [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(doSomething) userInfo:@"paramtest" repeats:NO];
使用NSTimer時註意事項可以參考我的另外一篇博客《實現定時器NSTimer的高逼格方法》
(3)sleep(NSThreed)
[NSThread sleepForTimeInterval:1.0f]; // 這裡執行延遲處理代碼 [self doSomething];
三、dispatch_once 和 dispacth_apply
dispatch_once整個app運行周期內只執行一次代碼,多用於單例模式。
dispatch_once_t *predicate:一個全局的變數 dispatch_block_t block:block函數塊
dispatch_apply讓指定代碼按設定次數多次執行,dispatch_apply類似一個for迴圈,會在指定的dispatch queue中運行block任務n次,如果隊列是併發隊列,則會併發執行block任務,如果隊列是串列隊列,則block任務只會同步執行,但是dispatch_apply是一個同步調用,block任務執行n次後才返回。
size_t iterations:執行次數 dispatch_queue_t queue:隊列 void (^block)(size_t):block函數塊
代碼示例:
(1)dispatch_once
自定義block函數塊
//定義block typedef void (^BLOCK)(void); //將執行代碼封裝到block中 BLOCK myBlock = ^(){ static int count = 0; NSLog(@"count=%d",count++); };
執行
// 只會執行一次 static dispatch_once_t predicate; dispatch_once(&predicate, myBlock);
列印結果:count = 0;
(2) dispatch_apply
自定義block
//定義block typedef void (^BLOCK)(size_t); //將函數封裝到block BLOCK myBlock = ^(size_t size){ static int count = 0; NSLog(@"count=%d",count++); };
執行
dispatch_apply(5, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), myBlock);
列印結果:
count = 0 count = 2 count = 3 count = 1 count = 4
顯而易見,如果dispatch_apply的隊列是自定義的串列隊列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
輸出結果將是:
count = 0 count = 1 count = 2 count = 3 count = 4
dispatch_apply 可以處理一個任務重覆執行次數量級比較大的應用場景,假設從伺服器上獲取一組數組數據(超過100個元素對象)然後進行字典轉化模型
多線程併發處理:
// 多線程併發處理,可能造成線程爆炸及死鎖 dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT); for (int i = 0; i < 999; i++){ dispatch_async(queue, ^{ // 字典轉模型 }); } dispatch_barrier_sync(dispatch_get_main_queue(), ^{ NSLog(@"主線程更新"); }); ---------------------------這裡是分割線--------------------------- // dispatch_apply 方式 (優先選擇) NSArray *dictArray = nil; dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ dispatch_apply(dictArray.count, queue, ^(size_t index){ //字典轉模型 }); dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"主線程更新"); }); });
四、Dispatch Group
在追加到Dispatch Queue中的多個任務處理全部完畢之後想執行結束處理。如果只是使用一個Serial Dispatch Queue(串列隊列)時,只要將想執行的處理全部追加到該串列隊列中併在最後追加結束處理即可,但是在使用Concurrent Queue 時,可能會同時使用多個Dispatch Queue時,這就需要使用Dispatch Group。
- (void)testDispatchGroup{ dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_queue_create("com.gcdgroup.www", DISPATCH_QUEUE_CONCURRENT); dispatch_group_async(group, queue, ^{ for (int i = 0; i < 10; i++) { if (i == 9) { NSLog(@"test001"); } } }); dispatch_group_async(group, queue, ^{ NSLog(@"test002"); }); dispatch_group_async(group, queue, ^{ NSLog(@"test003"); }); dispatch_group_notify(group, queue, ^{ NSLog(@"全部完成"); }); }
列印結果:
2017-07-06 23:30:57.449 Test[8724:1743565] test002 2017-07-06 23:30:57.449 Test[8724:1743547] test003 2017-07-06 23:30:57.449 Test[8724:1743549] test001 2017-07-06 23:30:57.449 Test[8724:1743547] 全部完成
Dispatch Group廣泛運用到非同步獲取網路數據最後彙總的情況,如非同步獲取多張網路圖片資源後拼接成一張圖片等等。
五、dispatch_barrier_async
在訪問資料庫和文件時,如前所述,使用Serial Dispatch Queue可避免數據資源的競爭問題。眾所周知,寫處理與寫處理,寫處理與讀處理會發生數據一致性或數據競爭問題,但是讀處理與讀處理之前不存在數據一致性問題,為了提高效率我們可以這樣設想:讀處理可以追加到Concurrent Dispatch Queue(併發隊列)中,而寫處理在任意一個沒有讀取處理執行的狀態下追加到Serial Dispatch Queue(串列隊列)中(在寫處理結束之前,讀處理不可執行)。
代碼示例:
- (void)testDispatchBarrier{ dispatch_queue_t queue = dispatch_queue_create("com.gcdbarrier.www", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ NSLog(@"block001_read"); }); dispatch_async(queue, ^{ NSLog(@"block002_read"); }); dispatch_async(queue, ^{ NSLog(@"block003_read"); }); dispatch_barrier_sync(queue, ^{ NSLog(@"block004_write"); }); dispatch_async(queue, ^{ NSLog(@"block005_read"); }); dispatch_async(queue, ^{ NSLog(@"block006_read"); }); dispatch_async(queue, ^{ NSLog(@"block007_read"); }); }
列印結果:
2017-07-06 23:59:27.936 Test[9080:1907162] block003_read 2017-07-06 23:59:27.936 Test[9080:1907194] block002_read 2017-07-06 23:59:27.936 Test[9080:1907163] block001_read 2017-07-06 23:59:27.937 Test[9080:1907028] block004_write 2017-07-06 23:59:27.937 Test[9080:1907163] block005_read 2017-07-06 23:59:27.937 Test[9080:1907162] block007_read 2017-07-06 23:59:27.937 Test[9080:1907194] block006_read
六、dispatch_suspend / dispatch_resume
dispatch_suspend,dispatch_resume提供了“掛起、恢復”隊列的功能,簡單來說,就是可以暫停、恢復隊列上的任務。但是這裡的“掛起”,並不能保證可以立即停止隊列上正在運行的block,而是在當前block執行完成後,暫停後續的block執行。
// 掛起指定隊列 dispatch_suspend(queue); // 恢復指定隊列 dispatch_resume(queue);
代碼示例:
- (void)gcdSuspendResume{ dispatch_queue_t queue = dispatch_queue_create("com.test.gcd", DISPATCH_QUEUE_SERIAL); // 提交第一個block,延時5秒列印。 dispatch_async(queue, ^{ sleep(5); NSLog(@"After 5 seconds..."); }); // 提交第二個block,也是延時5秒列印 dispatch_async(queue, ^{ sleep(5); NSLog(@"After 5 seconds again..."); }); // 延時一秒 NSLog(@"sleep 1 second..."); sleep(1); // 掛起隊列 NSLog(@"suspend..."); dispatch_suspend(queue); // 延時10秒 NSLog(@"sleep 10 second..."); sleep(10); // 恢復隊列 NSLog(@"resume..."); dispatch_resume(queue); }
列印結果:
2017-07-07 14:29:44.329 beck.wang[1045:77001] sleep 1 second... 2017-07-07 14:29:45.330 beck.wang[1045:77001] suspend... 2017-07-07 14:29:45.330 beck.wang[1045:77001] sleep 10 second... 2017-07-07 14:29:49.333 beck.wang[1045:77045] After 5 seconds... 2017-07-07 14:29:55.331 beck.wang[1045:77001] resume... 2017-07-07 14:30:00.336 beck.wang[1045:77045] After 5 seconds again...
七、Dispatch Semaphore
dispatch_semaphore(信號量)是基於計數器的一種多線程同步機制,是GCD控制併發的一種方式。
信號量是一個整形值並且具有一個初始計數值,並且支持兩個操作:信號通知和等待。當一個信號量被信號通知,其計數會被增加。當一個線程在一個信號量上等待時,線程會被阻塞(如果有必要的話),直至計數器大於零,然後線程會減少這個計數。
在GCD中有三個函數是semaphore的操作,分別是:dispatch_semaphore_create、dispatch_semaphore_signal、dispatch_semaphore_wait。
1、dispatch_semaphore_create 創建具有初始值的信號量
// 輸出一個dispatch_semaphore_t類型且值為value的信號量。這裡的傳入的參數value必須>=0,否則dispatch_semaphore_create會返回NULL。 dispatch_semaphore_t dispatch_semaphore_create(long value); // 示例 dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
2、dispatch_semaphore_signal 發送信號量,讓信號量總數+1
long dispatch_semaphore_signal(dispatch_semaphore_tdsema)
3、dispatch_semaphore_wait 等待信號量,當信號總量< 0 的時候等待設置的timeout參數,否則就可以正常的執行,並讓信號總量 -1
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
註意:timeout是dispatch_time_t類型,不可直接使用其他類型(int或者float等)。如果等待的期間desema的值被dispatch_semaphore_signal函數加1了,且dispatch_semaphore_wait所處線程獲得了信號量,那麼就繼續向下執行並將信號量減1;如果等待期間沒有獲取到信號量或者信號量的值一直為0,那麼等到timeout時,其所處線程自動執行其後語句。
蘋果給了兩個timeout的巨集定義,也是比較常用的。
DISPATCH_TIME_NOW //當前時間 DISPATCH_TIME_FOREVER // 一直等待
如果需要自定義timeout可以使用
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta); dispatch_time_t dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);
喜歡我的文章請點擊關註哦,我將在以後的工作中爭取寫出更高質量的博客,交流分享!……^_^