iOS 多線程系列 -- 基礎概述
iOS 多線程系列 -- pthread
iOS 多線程系列 -- NSThread
iOS 多線程系列 -- GCD全解一(基礎)
iOS 多線程系列 -- GCD全解二(常用方法)
iOS 多線程系列 -- GCD全解三(進階)
iOS 多線程系列 -- NSOperation
測試Demo的GitHub地址
3. GCD常用方法
3.1 Dispatch Group
Dispatch Group 可以讓我們很方便的控制多線程中任務執行順序。假設這樣一種需求,有三個任務OA/OB/OC,我們想讓OC在OAOB執行完畢在執行,有幾種實現方式?方式有很多,詳細看下面總結,其中一種方式我們就可以用Dispatch Group實現。
- 數據類型:
- dispatch_group_t : 組對象,調用dispatch_group_create()創建
常用方法:
- dispatch_group_async/dispatch_group_async_f ,異步方式將代碼塊block放入隊列queue中執行,并將隊列關聯到調度組group。dispatch_group會等和它關聯的所有的dispatch_queue_t上的任務都執行完畢才會發出同步信號,執行dispathc_group_notify的代碼塊block,同時dispatch_group_wati會結束等待。一個group可以關聯多個任務隊列,下面示例代碼中可以查看。
dispatch_group_async(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i<10; i++) {
NSLog(@"---group1--%d---%@",i,[NSThread currentThread]);
}
});
- dispatch_group_notify 調度組Dispatch Group中任務執行完畢后,會發出同步信號,執行dispatch_group_notify中的block,以此來實現線程同步,實現多線程任務順序控制。所以上面的OA/OB/OC問題可以這樣:
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
//任務OA
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
//任務OB
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//任務OC
});
- dispatch_group_wait 同步函數,會阻塞當前線程直到超時或者group任務完成。返回0表示任務完全執行完畢,非0表示超時。如果想要一直等待直到任務完成,可以把第二個時間參數設置為DISPATCH_TIME_FOREVER.因為同步,所以小心死鎖,盡量不要放在主線程調用此方法
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
- dispatch_group_enter 手動指示有一個block進入group.調用此函數表示另一個塊通過加入組除dispatch_group_async()之外的一種手段。調用此函數必須是與dispatch_group_leave()平衡,否則調度組會顯示一個有任務,不會調用dispatch_group_notify.
- dispatch_group_leave 手動指示group中一個block完成.調用此函數表示塊已完成,并通過除dispatch_group_async()之外的方法離開調度組.
- dispatch_group_enter和dispatch_group_leave的簡單理解
- 一種不同于dispatch_group_async的操作調度組的方法
- 當我們調用n次dispatch_group_enter后再調用n次dispatch_group_level時,dispatch_group_notify和dispatch_group_wait會收到同步信號
- 可以簡單的看做group中有一個表示任務數的屬性operationCount, dispatch_group_enter會讓operationCount加1, dispatch_group_level會讓operationCount減1.當operationCount為0的時候,表示group總任務執行完畢,會同步通知dispatch_group_notify和dispatch_group_wait
- 測試代碼
- (void)group
{
NSLog(@"---group-begin---%@",[NSThread currentThread]);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{// 異步添加任務到全局并發隊列,并關聯到調度組
for (int i = 0; i<2; i++) {
NSLog(@"---group1--%d---%@",i,[NSThread currentThread]);
}
});
dispatch_group_async(group, dispatch_get_main_queue(), ^{// 異步添加任務到主隊列,并關聯到調度組
for (int i = 0; i<2; i++) {
NSLog(@"---group2--%d---%@",i,[NSThread currentThread]);
}
});
NSLog(@"---group middle %@",[NSThread currentThread]);
dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // group中的任務都執行完畢后,才回執行這個block
NSLog(@"---dispatch_group_notify---%@",[NSThread currentThread]);
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"---group3-----%@",[NSThread currentThread]);
});
NSLog(@"---dispatch_group_notify middle %@",[NSThread currentThread]);
dispatch_group_async(group, dispatch_get_main_queue(), ^{
NSLog(@"---group4----%@",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"---second dispatch_group_notify---%@",[NSThread currentThread]);
});
});
NSLog(@"---before dispatch_group_wait %@",[NSThread currentThread]);
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)); // 同步函數,會阻塞當前線程,只要超時或者group任務完成,返回0表示任務完全執行完畢,非0表示超時
NSLog(@"---group-end---%@",[NSThread currentThread]);
}
- 打印結果:
2017-06-29 10:38:15.572 Test - 多線程[21587:754475] ---group-begin---<NSThread: 0x60000007d200>{number = 1, name = main}
2017-06-29 10:38:15.573 Test - 多線程[21587:754568] ---group1--0---<NSThread: 0x6000002742c0>{number = 3, name = (null)}
2017-06-29 10:38:15.573 Test - 多線程[21587:754475] ---group middle <NSThread: 0x60000007d200>{number = 1, name = main} //①
2017-06-29 10:38:15.573 Test - 多線程[21587:754568] ---group1--1---<NSThread: 0x6000002742c0>{number = 3, name = (null)} //②
2017-06-29 10:38:15.573 Test - 多線程[21587:754475] ---before dispatch_group_wait <NSThread: 0x60000007d200>{number = 1, name = main} //③
2017-06-29 10:38:20.575 Test - 多線程[21587:754475] ---group-end---<NSThread: 0x60000007d200>{number = 1, name = main} //④
2017-06-29 10:38:20.576 Test - 多線程[21587:754475] ---group2--0---<NSThread: 0x60000007d200>{number = 1, name = main}
2017-06-29 10:38:20.576 Test - 多線程[21587:754475] ---group2--1---<NSThread: 0x60000007d200>{number = 1, name = main} //⑤
2017-06-29 10:38:20.577 Test - 多線程[21587:754475] ---dispatch_group_notify---<NSThread: 0x60000007d200>{number = 1, name = main} //⑥
2017-06-29 10:38:20.577 Test - 多線程[21587:754475] ---dispatch_group_notify middle <NSThread: 0x60000007d200>{number = 1, name = main}
2017-06-29 10:38:20.577 Test - 多線程[21587:754568] ---group3-----<NSThread: 0x6000002742c0>{number = 3, name = (null)}
2017-06-29 10:38:20.578 Test - 多線程[21587:754475] ---group4----<NSThread: 0x60000007d200>{number = 1, name = main}
2017-06-29 10:38:20.578 Test - 多線程[21587:754475] ---second dispatch_group_notify---<NSThread: 0x60000007d200>{number = 1, name = main} //⑦
- 打印結果分析:
- 從①和②打印結果看, dispatch_group_async添加任務是異步的,不會阻塞當前線程
- 從③和④的打印時間差,可以看出當前線程被阻塞了5s,說明dispatch_group_wait是同步函數,會阻塞線程. 為什么阻塞當前線程的時間內沒有group2輸出,查看添加group2輸出的代碼發現,這個打印任務是異步添加到主線程的,主線程阻塞完畢以后才會繼續打印group2
- ③的打印順序還可以說明dispatch_group_notify也是異步添加任務,不會阻塞當前線程
- ⑥的打印是在group1和group2之后得出,notify中的block任務確實是在group關聯的任務執行完畢后才執行
- group3/group4的打印,以及④的輸出可以看出nofity可以多層嵌套
- 所有調用dispatch_group_async的地方查看一下代碼會發現,調度組group可以關聯到不同的隊列
3.2 柵欄
dispatch_barrier_sync
- 在一個dispatch queue分派隊列上提交用于同步執行的block代碼塊,類似dispatch_sync()但是會標記為障礙,可以控制多線程中任務執行順序.執行順序為:在其之前提交的任務先執行 -> dispatch_barrier_sync提交的任務 -> dispatch_barrier_sync之后提交的任務
- dispatch_barrier_sync是和并發隊列相關聯的,因為串行隊列中的任務本身就是順序執行的,不需要barrier技術
- 同步提交block到指定queue,
會阻塞當前線程
直到在他前面提交到此queue的任務執行完畢,然后執行barrier block,然后當前線程才能繼續往下執行任務 , 從下面測試打印結果的⑤⑥⑦,打印barrier之后才會打印after dispatch_barrier_sync可以驗證這一點 - 函數聲明如下:queue是指定的并發隊列,block是提交的代碼塊
void dispatch_barrier_sync(dispatch_queue_t queue,
DISPATCH_NOESCAPE dispatch_block_t block);
- 測試代碼如下:
NSLog(@"barrierSync begin");
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});
NSLog(@"before dispatch_barrier_sync");
dispatch_barrier_sync(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
NSLog(@"after dispatch_barrier_sync");
dispatch_async(queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----4-----%@", [NSThread currentThread]);
});
NSLog(@"barrierSync end");
- 測試代碼打印如下:
2017-06-29 12:28:15.355 Test - 多線程[22813:835755] barrierSync begin //①
2017-06-29 12:28:15.355 Test - 多線程[22813:835755] before dispatch_barrier_sync //②
2017-06-29 12:28:15.355 Test - 多線程[22813:835803] ----2-----<NSThread: 0x6000002692c0>{number = 4, name = (null)} //③
2017-06-29 12:28:15.355 Test - 多線程[22813:835805] ----1-----<NSThread: 0x608000260bc0>{number = 3, name = (null)} //④
2017-06-29 12:28:15.356 Test - 多線程[22813:835755] ----barrier-----<NSThread: 0x600000071880>{number = 1, name = main} //⑤
2017-06-29 12:28:15.356 Test - 多線程[22813:835755] after dispatch_barrier_sync //⑥
2017-06-29 12:28:15.356 Test - 多線程[22813:835755] barrierSync end //⑦
2017-06-29 12:28:15.356 Test - 多線程[22813:835805] ----3-----<NSThread: 0x608000260bc0>{number = 3, name = (null)} //⑧
2017-06-29 12:28:15.356 Test - 多線程[22813:835803] ----4-----<NSThread: 0x6000002692c0>{number = 4, name = (null)} //⑨
dispatch_barrier_async,基本和dispatch_barrier_sync類似的功能,區別在于:
- dispatch_barrier_async是提交異步執行的block代碼塊,不會阻塞當前線程,從下面測試打印結果的⑤⑥⑦after dispatch_barrier_sync在barrier之前打印可以驗證這一點
- 測試代碼:
NSLog(@"barrierAsync begin");
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});
NSLog(@"before dispatch_barrier_async");
dispatch_barrier_async(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
NSLog(@"after dispatch_barrier_async");
dispatch_async(queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----4-----%@", [NSThread currentThread]);
});
NSLog(@"barrierAsync end");
- 測試結果打印:
2017-06-29 12:29:32.048 Test - 多線程[22813:835755] barrierAsync begin //①
2017-06-29 12:29:32.049 Test - 多線程[22813:835755] before dispatch_barrier_async //②
2017-06-29 12:29:32.049 Test - 多線程[22813:835815] ----1-----<NSThread: 0x600000268e00>{number = 5, name = (null)} //③
2017-06-29 12:29:32.049 Test - 多線程[22813:837436] ----2-----<NSThread: 0x600000267540>{number = 6, name = (null)} //④
2017-06-29 12:29:32.049 Test - 多線程[22813:835755] after dispatch_barrier_async //⑤
2017-06-29 12:29:32.049 Test - 多線程[22813:837436] ----barrier-----<NSThread: 0x600000267540>{number = 6, name = (null)} //⑥
2017-06-29 12:29:32.049 Test - 多線程[22813:835755] barrierAsync end //⑦
2017-06-29 12:29:32.050 Test - 多線程[22813:837436] ----3-----<NSThread: 0x600000267540>{number = 6, name = (null)} //⑧
2017-06-29 12:29:32.050 Test - 多線程[22813:835815] ----4-----<NSThread: 0x600000268e00>{number = 5, name = (null)} //⑨
總結:
- 上面已經得出dispatch_barrier_async和dispatch_barrier_sync的重要區別是會不會阻塞當前線程,所以按需選擇使用哪種barrier技術
- 如果在主線程需要用到,推薦dispatch_barrier_async因為不會阻塞線程,如果在barrier之前提交的任務中有耗時任務,也不會帶來主線程UI的卡頓
3.3 迭代dispatch_apply
dispatch_apply : 提交block任務塊到調度隊列進行多次調度.
三個參數解析:
- iterations表示調度總次數;
- queue任務提交的隊列,如果是并發隊列,那么調度可以并發執行,提高調用次數
- (^block)(size_t),帶有一個size_t參數的block塊, size_t類型的參數是當前迭代索引 ; block是任務塊
void dispatch_apply(size_t iterations, dispatch_queue_t queue, DISPATCH_NOESCAPE void (^block)(size_t));
NOTE:
- dispatch_apply是同步函數,會阻塞當前線程,直到迭代完畢,由③可以驗證.
- dispatch_apply可能會提高迭代速度.你可以指定串行或并發 queue,并發queue允許同時執行多個循環迭代,而串行queue就沒太大必要使用了
- 如果當前隊列是串行隊列,而且在當前串行隊列中使用dispatch_apply指定當前隊列為迭代隊列,會死鎖
- 并發隊列中,迭代次序是不定的,有測試打印結果①②可以驗證
測試代碼:
NSLog(@"apply begin");
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"index = %zd , thread = %@",index,[NSThread currentThread]);
});
NSLog(@"apply end");
測試打印結果:
2017-06-29 14:46:08.973 Test - 多線程[24285:925046] apply begin
2017-06-29 14:46:08.973 Test - 多線程[24285:925046] index = 0 , thread = <NSThread: 0x608000079c00>{number = 1, name = main}
2017-06-29 14:46:08.973 Test - 多線程[24285:925297] index = 1 , thread = <NSThread: 0x608000268600>{number = 7, name = (null)} //①
2017-06-29 14:46:08.973 Test - 多線程[24285:925094] index = 3 , thread = <NSThread: 0x60000026cb40>{number = 5, name = (null)} //②
2017-06-29 14:46:08.973 Test - 多線程[24285:925296] index = 2 , thread = <NSThread: 0x60800026ac80>{number = 6, name = (null)}
2017-06-29 14:46:08.974 Test - 多線程[24285:925046] apply end //③
3.4 一次執行dispatch_once
dispatch_once的作用:保證其block代碼塊中的代碼只執行一次,是線程安全的,常用于實現單例等
數據類型
- dispatch_once_t , 其實就是long類型
typedef long dispatch_once_t;
方法,我們在使用的時候,是一個dispatch_once的宏,對應的是一個_dispatch_once的函數._dispatch_once函數兩個參數,第一個predicate是標志位,第二個是代碼塊。
線程安全簡單理解:
- 第一次執行,block需要被調用,調用結束后需要置標記變量
- 非第一次執行,而此時第一次執行尚未完成,線程需要等待第一次執行完成后才能繼續往下執行
- 非第一次執行,而此時#1已經完成,線程直接跳過block而進行后續任務
- 更詳細的深入探究可以看這里一步步分析dispatch_once的低負載特性
void
_dispatch_once(dispatch_once_t *predicate,
DISPATCH_NOESCAPE dispatch_block_t block)
{
if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
dispatch_once(predicate, block);
} else {
dispatch_compiler_barrier();
}
DISPATCH_COMPILER_CAN_ASSUME(*predicate == ~0l);
}
測試代碼
dispatch_apply(4, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"------index = %zd",index);
static dispatch_once_t onceToken; //①
dispatch_once(&onceToken, ^{//②
NSLog(@"------run");
});
});
測試打印結果
2017-06-29 15:11:57.542 Test - 多線程[24619:946139] ------index = 1
2017-06-29 15:11:57.542 Test - 多線程[24619:946049] ------index = 0
2017-06-29 15:11:57.542 Test - 多線程[24619:946136] ------index = 2
2017-06-29 15:11:57.542 Test - 多線程[24619:946139] ------run
3.5 延遲 dispatch_after
dispatch_after(dispatch_time_t when,
dispatch_queue_t queue,
dispatch_block_t block);
- dispatch_after,延遲指定時間when后,提交block任務到隊列queue。
注意:并不是延時執行任務
- 測試代碼:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"run-----"); // 延遲2秒后提交任務
});
3.6 線程狀態操作: 掛起和恢復隊列
- 當需要掛起隊列時,使用dispatch_suspend方法;
- 恢復隊列時,使用dispatch_resume方法
注意點:
- dispatch_suspend 并不會立即掛起隊列,根據API文檔可以查看到。調用dispatch_suspend函數后,當前正在執行的block任務會繼續執行下面的測試打印結果①②可以驗證;只是其后添加的任務會被掛起,直到調用dispatch_resume恢復隊列,從下面的測試打印結果③④⑤順序可以驗證
- dispatch_suspend 可以掛起串行、并發隊列,但是不能掛起全局并發隊列,下面測試代碼中打開test1可以自行驗證
- dispatch_suspend 和dispatch_resume必須成對調用。可以簡單理解為:dispatch_suspend會讓隊列的某個計數器加1,dispatch_resume會讓這個計數器減1 , 只有當這個計數器為0的時候才回恢復隊列
- 以上注意點,可以從下面測試打印結果中驗證。
測試代碼:
- (void)testsuplend
{
//test1
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // dispatch_suspend對全局并發隊列無效
//test2
// dispatch_queue_t queue = dispatch_queue_create(NULL, 0); // dispatch_suspend 對串行隊列有效
//test3
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT); // dispatch_suspend 對自己創建的并發隊列有效
dispatch_async(queue, ^{
for (int i = 0 ; i < 10; i ++) {
NSLog(@"---suplend1 -- %zd thread = %@",i,[NSThread currentThread]);
sleep(1);
}
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"dispatch_suspend queue one , suplend1打印任務執行完畢后會掛起隊列,直到dispatch_resume調用后恢復隊列");
dispatch_suspend(queue);
});
// 4s后繼續添加新的任務,如果在添加之前點擊掛起,此任務不會執行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_async(queue, ^{
for (int i = 0 ; i < 10; i ++) {
sleep(2);
NSLog(@"---suplend2 -- %zd thread = %@",i,[NSThread currentThread]);
}
});
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"dispatch_resume queue one ,---suplend2 --打印任務會恢復執行");
dispatch_resume(queue);
});
}
測試打印結果:
2017-06-29 23:31:56.678 Test - 多線程[57328:8371175] ---suplend1 -- 0 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
2017-06-29 23:31:57.680 Test - 多線程[57328:8371175] ---suplend1 -- 1 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
2017-06-29 23:31:58.682 Test - 多線程[57328:8371175] ---suplend1 -- 2 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
2017-06-29 23:31:58.777 Test - 多線程[57328:8371126] dispatch_suspend queue one , suplend1打印任務執行完畢后會掛起隊列,直到dispatch_resume調用后恢復隊列 //①
2017-06-29 23:31:59.687 Test - 多線程[57328:8371175] ---suplend1 -- 3 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}//②
2017-06-29 23:32:00.689 Test - 多線程[57328:8371175] ---suplend1 -- 4 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
2017-06-29 23:32:01.690 Test - 多線程[57328:8371175] ---suplend1 -- 5 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
2017-06-29 23:32:02.677 Test - 多線程[57328:8371126] dispatch_resume queue one ,---suplend2 --打印任務會恢復執行//③
2017-06-29 23:32:02.692 Test - 多線程[57328:8371175] ---suplend1 -- 6 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
2017-06-29 23:32:03.695 Test - 多線程[57328:8371175] ---suplend1 -- 7 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}//④
2017-06-29 23:32:04.682 Test - 多線程[57328:8371177] ---suplend2 -- 0 thread = <NSThread: 0x60000027b7c0>{number = 4, name = (null)}//⑤
2017-06-29 23:32:04.699 Test - 多線程[57328:8371175] ---suplend1 -- 8 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
2017-06-29 23:32:05.702 Test - 多線程[57328:8371175] ---suplend1 -- 9 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}