引言: 越是細節越能體現一個人的嚴謹,越是微小越是能看到事物的光芒
1. 隊列
官方解釋:
DispathQueue是FIFO隊列,應用程序可以以塊對象的形式向其提交任務。調度隊列可以串行執行任務,也可以并發執行任務。提交給DispathQueue的工作在系統管理的線程池上執行,不用開發者操心在那個線程上。除了App主線程的調度隊列mainQueue外,別的DispathQueue系統不保證它執行任務的時候一直在一個具體的線程中。您可以同步或異步地安排工作項。當您同步地計劃一個工作項時,代碼將等待該項完成執行。當您異步地計劃工作項時,代碼在工作項運行到其他地方時繼續執行。
避免創建過多線程
如果使用DispathQueue去并發執行任務時,不要再執行的時候阻塞任務執行的線程。如果阻塞了,系統會再額外創建線程去執行別的任務,如果任務快太多,系統可能會耗盡應用程序的線程。應用程序消耗太多線程的另一種方式是創建太多私有并發調度隊列。因為每個調度隊列都消耗線程資源,所以創建額外的并發調度隊列會加劇線程消耗問題。不要創建私有并發隊列,而是將任務提交到一個全局global并發調度隊列。對于串行任務,請將串行隊列的目標設置為全局并發隊列之一。這樣,您可以保持隊列的序列化行為,同時最小化創建線程的獨立隊列的數量。
dispatch_queue_t dySerial = dispatch_queue_create("自己創建串行隊列", DISPATCH_QUEUE_SERIAL); //自己創建串行隊列
dispatch_queue_t dyConcurrent = dispatch_queue_create("自己創建并行隊列", DISPATCH_QUEUE_CONCURRENT); //自己創建并行隊列
dispatch_queue_t osMainSerial = dispatch_get_main_queue(); //系統默認主隊列,就是主線程
dispatch_queue_t osConcurrent = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //系統默認的全局并行隊列
2. 執行
//異步執行
dispatch_async("傳入隊列", 任務block);
//同步執行
dispatch_sync("傳入隊列", 任務block);
3. 不同執行 + 不同隊列效果
3.1 同步執行 + 并行隊列 不會創建多線程 順序執行
//當前線程打印標記
NSLog(@"打印當前線程---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"*********同步執行 + 并行隊列 不會創建多線程 順序執行*********");
dispatch_sync(dyConcurrent, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任務1線程---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_sync(dyConcurrent, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任務2線程---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_sync(dyConcurrent, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任務3線程---%@",[NSThread currentThread]); // 打印當前線程
}
});
2018-02-26 15:58:11.853843+0800 GCD 系列知識點[47527:3342566] 打印當前線程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.854101+0800 GCD 系列知識點[47527:3342566] *********同步執行 + 并行隊列 不會創建多線程 順序執行*********
2018-02-26 15:58:11.854512+0800 GCD 系列知識點[47527:3342566] 任務1線程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.855069+0800 GCD 系列知識點[47527:3342566] 任務1線程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.855570+0800 GCD 系列知識點[47527:3342566] 任務2線程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.856073+0800 GCD 系列知識點[47527:3342566] 任務2線程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.856731+0800 GCD 系列知識點[47527:3342566] 任務3線程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.857079+0800 GCD 系列知識點[47527:3342566] 任務3線程---<NSThread: 0x600000069600>{number = 1, name = main}
3.2 異步執行 + 并發隊列 會創建多個線程
NSLog(@"*********異步執行 + 并發隊列 會創建多個線程 不是順序執行*********");
dispatch_async(dyConcurrent, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任務1線程---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(dyConcurrent, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任務2線程---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(dyConcurrent, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任務3線程---%@",[NSThread currentThread]); // 打印當前線程
}
});
2018-02-26 15:59:55.983225+0800 GCD 系列知識點[47573:3349914] 打印當前線程---<NSThread: 0x600000076140>{number = 1, name = main}
2018-02-26 15:59:55.983441+0800 GCD 系列知識點[47573:3349914] *********異步執行 + 并發隊列 會創建多個線程 不是順序執行*********
2018-02-26 15:59:55.983760+0800 GCD 系列知識點[47573:3350179] 任務2線程---<NSThread: 0x604000679b80>{number = 5, name = (null)}
2018-02-26 15:59:55.983795+0800 GCD 系列知識點[47573:3350180] 任務1線程---<NSThread: 0x6000002798c0>{number = 4, name = (null)}
2018-02-26 15:59:55.983821+0800 GCD 系列知識點[47573:3350190] 任務3線程---<NSThread: 0x60000027a240>{number = 6, name = (null)}
2018-02-26 15:59:55.984116+0800 GCD 系列知識點[47573:3350179] 任務2線程---<NSThread: 0x604000679b80>{number = 5, name = (null)}
2018-02-26 15:59:55.984126+0800 GCD 系列知識點[47573:3350180] 任務1線程---<NSThread: 0x6000002798c0>{number = 4, name = (null)}
2018-02-26 15:59:55.984190+0800 GCD 系列知識點[47573:3350190] 任務3線程---<NSThread: 0x60000027a240>{number = 6, name = (null)}
3.3 同步執行 + 串行隊列 不會創建新線程
NSLog(@"*********同步執行 + 串行隊列 不會創建新線程*********");
dispatch_sync(dySerial, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任務1線程---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_sync(dySerial, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任務2線程---%@",[NSThread currentThread]); // 打印當前線程
}
});
2018-02-26 16:02:43.429624+0800 GCD 系列知識點[47637:3363265] 打印當前線程---<NSThread: 0x604000078bc0>{number = 1, name = main}
2018-02-26 16:02:43.429817+0800 GCD 系列知識點[47637:3363265] *********同步執行 + 串行隊列 不會創建新線程*********
2018-02-26 16:02:43.430040+0800 GCD 系列知識點[47637:3363265] 任務1線程---<NSThread: 0x604000078bc0>{number = 1, name = main}
2018-02-26 16:02:43.430238+0800 GCD 系列知識點[47637:3363265] 任務1線程---<NSThread: 0x604000078bc0>{number = 1, name = main}
2018-02-26 16:02:43.430346+0800 GCD 系列知識點[47637:3363265] 任務2線程---<NSThread: 0x604000078bc0>{number = 1, name = main}
2018-02-26 16:02:43.430466+0800 GCD 系列知識點[47637:3363265] 任務2線程---<NSThread: 0x604000078bc0>{number = 1, name = main}
3.4 異步執行 + 串行隊列 只創建了一個線程(區別于當前線程的新線程)
NSLog(@"*********異步執行 + 串行隊列 只創建了1個線程*********");
dispatch_async(dySerial, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任務1線程---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(dySerial, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任務2線程---%@",[NSThread currentThread]); // 打印當前線程
}
});
2018-02-26 16:04:52.596042+0800 GCD 系列知識點[47689:3374647] 打印當前線程---<NSThread: 0x60000006f000>{number = 1, name = main}
2018-02-26 16:04:52.596243+0800 GCD 系列知識點[47689:3374647] *********異步執行 + 串行隊列 只創建了1個線程*********
2018-02-26 16:04:52.596596+0800 GCD 系列知識點[47689:3375109] 任務1線程---<NSThread: 0x60000046ab00>{number = 4, name = (null)}
2018-02-26 16:04:52.596912+0800 GCD 系列知識點[47689:3375109] 任務1線程---<NSThread: 0x60000046ab00>{number = 4, name = (null)}
2018-02-26 16:04:52.597323+0800 GCD 系列知識點[47689:3375109] 任務2線程---<NSThread: 0x60000046ab00>{number = 4, name = (null)}
2018-02-26 16:04:52.597453+0800 GCD 系列知識點[47689:3375109] 任務2線程---<NSThread: 0x60000046ab00>{number = 4, name = (null)}
3.5 同步/異步 + 主隊列dispatch_get_main_queue()執行效果
3.5.1 特別注意: 同步執行 + 對于特殊的串行隊列 dispatch_get_main_queue() 會出現死鎖
NSLog(@"*********特別注意 異步執行 + 對于特殊的串行隊列 dispatch_get_main_queue() 會出現死鎖 *********");
NSLog(@"任務1");
dispatch_sync(osMainSerial, ^{
NSLog(@"任務2線程---%@",[NSThread currentThread]); // 打印當前線程
});
NSLog(@"任務3");
Xcode運行 項目直接崩潰
首先執行任務1,然后遇到dispatch_sync 同步線程,當前線程進入等待,等待同步線程中的任務2執行完再執行任務3,這個任務2是加入到mainQueue主隊列中。
dispatch_sync(osMainSerial,^(void)()) 是同步一個任務到主隊列中,而當前去同步的正是主隊列。因為隊列的執行是FIFO(先進先出),所以兩個都在等待對方完成,就會造成死鎖。
image.png
3.5.2 如果這個操作放在非主隊列中執行就不會有問題
//但是如果dispatch_sync(osMainSerial,^void()) 這個任務的執行是放在 非主線程中(其他子線程) 執行的,就沒問題
//NSThread detachNewThread 開辟一個新線程執行方法
[NSThread detachNewThreadSelector:@selector(testOtherThreadGetMainSyncSomething) toTarget:self withObject:nil];
//測試 同步執行 + 主隊列dispatch_get_main_queue() 放在其他線程中
- (void)testOtherThreadGetMainSyncSomething{
dispatch_queue_t osMainSerial = dispatch_get_main_queue(); //系統默認主隊列,就是主線程
NSLog(@"任務1");
dispatch_sync(osMainSerial, ^{
NSLog(@"任務2線程---%@",[NSThread currentThread]); // 打印當前線程
});
NSLog(@"任務3");
}
2018-02-26 17:58:01.616851+0800 GCD 系列知識點[50280:3588248] 打印當前線程---<NSThread: 0x60400006aac0>{number = 1, name = main}
2018-02-26 17:58:01.617322+0800 GCD 系列知識點[50280:3588554] 任務1
2018-02-26 17:58:01.623797+0800 GCD 系列知識點[50280:3588248] 任務2線程---<NSThread: 0x60400006aac0>{number = 1, name = main}
2018-02-26 17:58:01.624153+0800 GCD 系列知識點[50280:3588554] 任務3
3.6 異步執行 + 對于特殊的串行隊列 dispatch_get_main_queue() 異步執行的時候 不會創建新線程,只會使用當前線程
NSLog(@"*********特別注意 異步執行 + 對于特殊的串行隊列 dispatch_get_main_queue() 異步執行的時候 不會創建新線程*********");
dispatch_async(osMainSerial, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任務1線程---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(osMainSerial, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任務2線程---%@",[NSThread currentThread]); // 打印當前線程
}
});
2018-02-26 17:52:39.747531+0800 GCD 系列知識點[50190:3564074] 打印當前線程---<NSThread: 0x60000006bf80>{number = 1, name = main}
2018-02-26 17:52:39.747691+0800 GCD 系列知識點[50190:3564074] *********特別注意 異步執行 + 對于特殊的串行隊列 dispatch_get_main_queue() 異步執行的時候 不會創建新線程*********
2018-02-26 17:52:39.773217+0800 GCD 系列知識點[50190:3564074] 任務1線程---<NSThread: 0x60000006bf80>{number = 1, name = main}
2018-02-26 17:52:39.773976+0800 GCD 系列知識點[50190:3564074] 任務1線程---<NSThread: 0x60000006bf80>{number = 1, name = main}
2018-02-26 17:52:39.775269+0800 GCD 系列知識點[50190:3564074] 任務2線程---<NSThread: 0x60000006bf80>{number = 1, name = main}
2018-02-26 17:52:39.776235+0800 GCD 系列知識點[50190:3564074] 任務2線程---<NSThread: 0x60000006bf80>{number = 1, name = main}
4. 關于死鎖
4.1 在上3.5.1上介紹 同步執行(dispatch_sync
) + 主隊列 = 死鎖。那么如果同步執行的 + 非主隊列 是否就一定不會出現死鎖了?看下面的代碼:
//測試 同步 + 串行隊列(非組隊列) 死鎖
[self testAsyncSerialLockSometing];
//測試死鎖 同步執行 + 串行隊列(非主隊列)死鎖的情況
/**
用異步執行一個串行隊列的到一個新的線程,然后再同步執行這個串行隊列,會發生死鎖
*/
- (void)testAsyncSerialLockSometing{
//這里自己創建一個串行隊列new dySerial
dispatch_queue_t dySerial = dispatch_queue_create("new dySerial", DISPATCH_QUEUE_SERIAL); //自己創建串行隊列
//先異步執行這個串行隊列,這樣可以得到一個子線程。這個串行隊列現在是掛載到
dispatch_async(dySerial, ^{
NSLog(@"任務1線程---%@",[NSThread currentThread]); // 打印當前線程
//這里再同步執行這個串行隊列
dispatch_sync(dySerial, ^{
NSLog(@"任務2線程---%@",[NSThread currentThread]); // 打印當前線程
});
NSLog(@"任務3線程---%@",[NSThread currentThread]); // 打印當前線程
});
}
2018-02-27 09:57:49.336615+0800 GCD 系列知識點[55106:3863926] 打印當前線程---<NSThread: 0x60400006efc0>{number = 1, name = main}
2018-02-27 09:57:49.336972+0800 GCD 系列知識點[55106:3864092] 任務1線程---<NSThread: 0x60400027ca80>{number = 3, name = (null)}
(lldb)
Thread 3: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
會發現還是會出現死鎖的情況,綜上所述不難發現這種死鎖產生的原因:在串行隊列A(主隊列或自己創建的串行隊列)的線程中同步執行一個任務,而且這個任務還是放在串行隊列A中。這樣就會出現死鎖。
4.2 還有一種死鎖,就是當前的串行隊列被阻塞,然后同步任務到這個隊列就會出現
//測試 主線程被阻塞 死鎖
[self testThreadBusyLockSomething];
- (void)testThreadBusyLockSomething{
dispatch_queue_t dyConcurrent = dispatch_queue_create("dyConcurrent", DISPATCH_QUEUE_CONCURRENT); //自己創建并行隊列
dispatch_async(dyConcurrent, ^{
NSLog(@"任務1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任務3");
});
NSLog(@"任務4");
});
NSLog(@"任務2");
//這里有個死循環
while(1){
}
}
2018-02-27 10:38:31.763153+0800 GCD 系列知識點[55527:3978731] 打印當前線程---<NSThread: 0x604000065500>{number = 1, name = main}
2018-02-27 10:38:31.763335+0800 GCD 系列知識點[55527:3978731] 任務2
2018-02-27 10:38:31.763358+0800 GCD 系列知識點[55527:3978980] 任務1
執行順序:
main隊列中死循環不結束,任務3不執行,任務3又是global全局隊列同步過去的,global中同步的任務3不執行完、任務4是不會執行的。
了解死鎖的發生時機后就很容易理解,所謂隊列(串行并行)就是一個順序執行的甬道。所謂同步異步就是決定執行這個隊列的時候是否開辟新的線程去執行。
4. 關于GCD之間的通信,就是一個概念記住就好。不同的隊列直接相互通信,只需要在執行任務的時候同步或異步執行任務的時候調用別的隊列傳值,最常見的就是
// 獲取全局并發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 獲取主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 異步追加任務
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
// 回到主線程
dispatch_async(mainQueue, ^{
// 追加在主線程中執行的任務
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
});
});
5. GCD常用方法
5.1 組操作dispatch_group
5.1.1 dispatch_group_notify、dispatch_group_wait 基本用法
異步執行多個耗時任務,然后多個任務都執行完畢后再回到主線程執行任務。這時候我們可以用到 GCD 的隊列組。
//用于創建任務組
dispatch_group_t dispatch_group_create(void);
//異步任務提交到指定任務組和指定下拿出隊列執行
dispatch_group_async(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
//多個異步任務完成后調用block(不會阻塞當前線程)
dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
//多個異步任務完成后dispatch_group_wait這句話后面的代碼才會執行(會阻塞當前線程,任務全部執行完畢或超時才會接觸阻塞)
//注意:因為dispatch_group_wait會阻塞當前線程,所以一般情況不要放入主線程
dispatch_group_wait(dispatch_group_t group,
dispatch_time_t timeout);
//使用的時候成對使用,在執行某個任務前調用enter,待執行任務數+1
//在某個任務完成后,調用leave,待執行任務數-1
//只有所有任務都完成(任務數為0)才會觸發dispatch_group_notify 或 dispatch_group_wait
dispatch_group_enter(dispatch_group_t group);
dispatch_group_leave(dispatch_group_t group);
看Demo
-(void)testGroup{
dispatch_group_t group = dispatch_group_create(); //創建zhu
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"doSomeThing1");
}) ;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"doSomeThing2");
}) ;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"doSomeThing3");
sleep(3);
}) ;
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新界面");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"接著做某事");
}
2018-02-28 16:16:29.172002+0800 GCD 系列知識點[14525:1509846] doSomeThing1
2018-02-28 16:16:29.172004+0800 GCD 系列知識點[14525:1509848] doSomeThing2
2018-02-28 16:16:29.172014+0800 GCD 系列知識點[14525:1509849] doSomeThing3
2018-02-28 16:16:32.174314+0800 GCD 系列知識點[14525:1509719] 接著做某事
2018-02-28 16:16:32.179732+0800 GCD 系列知識點[14525:1509719] 刷新界面
運行發現 ,所有任務都執行完畢了,NSLog(@"刷新界面");
這句代碼才執行,NSLog(@"接著做某事");
這句代碼才執行。
注意
1,dispatch_group_wait
會阻塞當前線程,什么時候在它之前group_async執行的block任務結束了,或者wait自己設置的時間超時了,代碼才會往下執行。如果dispatch_group_wait
之前沒有group_async block任務,它就會馬上執行
2,dispatch_group_wait
和 dispatch_group_notify
同時出現,只有dispatch_group_wait
執行后, dispatch_group_notify
才會執行,不管dispatch_group_wait
在 dispatch_group_notify
之前還是之后。
dispatch_group_wait
會阻塞當前線程,所以不要放在App主線程。
5.1.2 dispatch_group_enter、dispatch_group_leave 基本用法
在介紹這個用法之前,我們先看一個例子
-(void)testGroup{
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"doSomeThing1");
});
}) ;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"doSomeThing2");
}) ;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"doSomeThing3");
}) ;
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新界面");
});
}
2018-02-28 17:26:07.752043+0800 GCD 系列知識點[15102:1596389] doSomeThing2
2018-02-28 17:26:07.752051+0800 GCD 系列知識點[15102:1596391] doSomeThing3
2018-02-28 17:26:07.761563+0800 GCD 系列知識點[15102:1596119] 刷新界面
2018-02-28 17:26:09.756480+0800 GCD 系列知識點[15102:1596390] doSomeThing1
會發現doSomeThing1
還沒執行完就進入dispatch_group_notify
打印刷新頁面,這是為什么了?
看代碼會發現 doSomeThing1
是放在了一個異步線程中執行的。但是dispatch_group_notify
只會監聽dispatch_group_async
中執行的同步任務。如果dispatch_group_async
執行的是個異步任務,那notify 監聽結果可能不是你想要的。
那我們能不能實現dispatch_group_async
執行多異步任務,同時還能監聽到任務全部完成了?做到這些就需要dispatch_group_enter
和 dispatch_group_leave
了,看下面例子:
-(void)testGroup{
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"doSomeThing1");
dispatch_group_leave(group);
});
}) ;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"doSomeThing2");
}) ;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"doSomeThing3");
}) ;
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新界面");
});
}
2018-02-28 17:34:10.449511+0800 GCD 系列知識點[15199:1621095] doSomeThing3
2018-02-28 17:34:10.449512+0800 GCD 系列知識點[15199:1621093] doSomeThing2
2018-02-28 17:34:12.449752+0800 GCD 系列知識點[15199:1621094] doSomeThing1
2018-02-28 17:34:12.450328+0800 GCD 系列知識點[15199:1620866] 刷新界面
dispatch_group_enter 用來標記這個任務的開始,dispatch_group_leave用來標記任務真正完成。這樣成對標記可以實現標記dispatch_group_async
任務的真正執行完畢,dispatch_group_notify
也就能監聽到了。
記住:
1. dispatch_group_enter 和 dispatch_group_leave 是成對出現的,不然dispatch_group 隊列會出錯。
2. dispatch_group_enter調用n次,就需要dispatch_group_leave調用n次,這樣dispatch_group_notify才會執行
3. dispatch_group_enter/dispatch_group_leave 可以在mainQuene上用,不會像Semaphore卡住主線程
5.1.3 用dispatch_group 處理多網絡請求都返回在刷新頁面
根據5.1.2 我們可以在實際開發中運用dispatch_group 來處理多個網絡請求都完成的通知。因為我們的網絡請求都是異步的,正好可以用dispatch_group_enter 和 dispatch_group_leave
實現標記完成。
下面寫一段偽代碼展示:
dispatch_group_t group = dispatch_group_create();
//網絡請求1
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[異步網絡請求1:{
成功Block:dispatch_group_leave(group);
失敗Block:dispatch_group_leave(group);
}];
})
//網絡請求2
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[異步網絡請求3:{
成功Block:dispatch_group_leave(group);
失敗Block:dispatch_group_leave(group);
}];
})
//網絡請求3
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[異步網絡請求3:{
成功Block:dispatch_group_leave(group);
失敗Block:dispatch_group_leave(group);
}];
})
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有網絡請求完畢");
});
這樣就可以處理多網絡請求同時完成的情況了。
5.2 只執行一次dispatch_once
我們在創建單例、或者有整個程序運行過程中只執行一次的代碼時,我們就用到了 GCD 的 dispatch_once 函數。使用
dispatch_once 函數能保證某段代碼在程序運行過程中只被執行1次,并且即使在多線程的環境下,dispatch_once也可以保證線程安全。
- (void)test {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執行1次的代碼(這里面默認是線程安全的)
});
}
5.3 延時執行方法:dispatch_after
在指定時間(例如3秒)之后執行某個任務。可以用 GCD 的dispatch_after函數來實現。
dispatch_after函數并不是在指定時間之后才開始執行處理,而是在指定時間之后將任務追加到主隊列中。嚴格來說,這個時間并不是絕對準確的,但想要大致延遲執行任務,dispatch_after函數是很有效的
- (void)after {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0秒后異步追加任務代碼到主隊列,并開始執行
NSLog(@"after---%@",[NSThread currentThread]); // 打印當前線程
});
}
5.4 柵欄方法dispatch_barrier_async
我們有時需要異步執行兩組操作,而且第一組操作執行完之后,才能開始執行第二組操作。這樣我們就需要一個相當于柵欄一樣的一個方法將兩組異步執行的操作組給分割起來,當然這里的操作組里可以包含一個或多個任務。這就需要用到dispatch_barrier_async方法在兩個操作組間形成柵欄。
如下,如果想
-(void)testBarrier{
dispatch_queue_t dyConcurrent = dispatch_queue_create("dyConcurrent1", DISPATCH_QUEUE_CONCURRENT); //自己創建并行隊列
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dyConcurrent, ^{
for (int i = 0; i< 3; i++) {
NSLog(@"任務1");
}
});
dispatch_group_async(group, dyConcurrent, ^{
for (int i = 0; i< 3; i++) {
NSLog(@"任務2");
}
});
dispatch_barrier_async(dyConcurrent, ^{
NSLog(@"追加追加追加");
});
dispatch_group_async(group, dyConcurrent, ^{
for (int i = 0; i< 3; i++) {
NSLog(@"任務3");
}
});
dispatch_group_async(group, dyConcurrent, ^{
for (int i = 0; i< 3; i++) {
NSLog(@"任務4");
}
});
}
2018-03-01 09:16:35.722991+0800 GCD 系列知識點[16166:1788483] 打印當前線程---<NSThread: 0x60400007f880>{number = 1, name = main}
2018-03-01 09:16:35.723276+0800 GCD 系列知識點[16166:1788561] 任務1
2018-03-01 09:16:35.723288+0800 GCD 系列知識點[16166:1788563] 任務2
2018-03-01 09:16:35.723442+0800 GCD 系列知識點[16166:1788561] 任務1
2018-03-01 09:16:35.723452+0800 GCD 系列知識點[16166:1788563] 任務2
2018-03-01 09:16:35.723532+0800 GCD 系列知識點[16166:1788561] 任務1
2018-03-01 09:16:35.723549+0800 GCD 系列知識點[16166:1788563] 任務2
2018-03-01 09:16:35.723785+0800 GCD 系列知識點[16166:1788563] 追加追加追加
2018-03-01 09:16:35.724089+0800 GCD 系列知識點[16166:1788560] 任務3
2018-03-01 09:16:35.724357+0800 GCD 系列知識點[16166:1788563] 任務4
2018-03-01 09:16:35.725130+0800 GCD 系列知識點[16166:1788560] 任務3
2018-03-01 09:16:35.725511+0800 GCD 系列知識點[16166:1788563] 任務4
2018-03-01 09:16:35.725511+0800 GCD 系列知識點[16166:1788560] 任務3
2018-03-01 09:16:35.725900+0800 GCD 系列知識點[16166:1788563] 任務4
會發現,任務1和任務2 全部執行完畢后 在執行追加任務,然后再執行任務3和任務4,如果我們下面代碼注釋掉再運行一下
/*
dispatch_barrier_async(dyConcurrent, ^{
NSLog(@"追加追加追加");
});
*/
2018-03-01 09:19:36.735062+0800 GCD 系列知識點[16214:1797692] 任務1
2018-03-01 09:19:36.735062+0800 GCD 系列知識點[16214:1797691] 任務2
2018-03-01 09:19:36.735089+0800 GCD 系列知識點[16214:1797705] 任務3
2018-03-01 09:19:36.735116+0800 GCD 系列知識點[16214:1797693] 任務4
2018-03-01 09:19:36.735310+0800 GCD 系列知識點[16214:1797691] 任務2
2018-03-01 09:19:36.735315+0800 GCD 系列知識點[16214:1797692] 任務1
2018-03-01 09:19:36.735436+0800 GCD 系列知識點[16214:1797705] 任務3
2018-03-01 09:19:36.735623+0800 GCD 系列知識點[16214:1797693] 任務4
2018-03-01 09:19:36.735653+0800 GCD 系列知識點[16214:1797691] 任務2
2018-03-01 09:19:36.735716+0800 GCD 系列知識點[16214:1797692] 任務1
2018-03-01 09:19:36.736728+0800 GCD 系列知識點[16214:1797705] 任務3
2018-03-01 09:19:36.738247+0800 GCD 系列知識點[16214:1797693] 任務4
會發現各個任務是混合異步執行的。
通過這個例子我們能明白dispatch_barrier_async主要是分割異步任務的作用。
5.5 dispatch_apply 快速遍歷
//參數iterations遍歷次數,queue執行遍歷任務的隊列,block每次遍歷回調
dispatch_apply(size_t iterations, dispatch_queue_t queue,
DISPATCH_NOESCAPE void (^block)(size_t));
和一般我們用for循環遍歷不同的是,如果dispatch_apply
調用的時候傳入queue
的是并發隊列,那么它的遍歷就是異步執行的,會在多個線程中進行,而我們直接for循環都是在當前線程中進行的。如下
-(void)testApply{
NSArray *imageArray = @[@"image1",@"image2",@"image3",@"image4",@"image5",@"image6",@"image7",@"image8"];
// dispatch_queue_t queue = dispatch_queue_create("new dySerial", DISPATCH_QUEUE_SERIAL); //自己創建串行隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(imageArray.count, queue, ^(size_t index) {
NSLog(@"%@---%@",imageArray[index], [NSThread currentThread]);
});
}
018-03-01 09:49:13.824457+0800 GCD 系列知識點[16580:1890506] image1---<NSThread: 0x600000064e80>{number = 1, name = main}
2018-03-01 09:49:13.824554+0800 GCD 系列知識點[16580:1890719] image3---<NSThread: 0x60400047be00>{number = 5, name = (null)}
2018-03-01 09:49:13.824555+0800 GCD 系列知識點[16580:1890718] image2---<NSThread: 0x600000265780>{number = 4, name = (null)}
2018-03-01 09:49:13.824601+0800 GCD 系列知識點[16580:1890721] image4---<NSThread: 0x6040004647c0>{number = 3, name = (null)}
2018-03-01 09:49:13.824706+0800 GCD 系列知識點[16580:1890506] image5---<NSThread: 0x600000064e80>{number = 1, name = main}
2018-03-01 09:49:13.824805+0800 GCD 系列知識點[16580:1890719] image6---<NSThread: 0x60400047be00>{number = 5, name = (null)}
2018-03-01 09:49:13.824987+0800 GCD 系列知識點[16580:1890721] image8---<NSThread: 0x6040004647c0>{number = 3, name = (null)}
2018-03-01 09:49:13.824991+0800 GCD 系列知識點[16580:1890718] image7---<NSThread: 0x600000265780>{number = 4, name = (null)}
會發現打印的線程號都是不同的。但是如果傳入的是個串行隊列(非主隊列)它就和普通的for循環沒什么不同了。如下
-(void)testApply{
NSArray *imageArray = @[@"image1",@"image2",@"image3",@"image4",@"image5",@"image6",@"image7",@"image8"];
dispatch_queue_t dySerial = dispatch_queue_create("new dySerial", DISPATCH_QUEUE_SERIAL); //自己創建串行隊列
// dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(imageArray.count, dySerial, ^(size_t index) {
NSLog(@"%@---%@",imageArray[index], [NSThread currentThread]);
});
}
2018-03-01 09:42:20.361535+0800 GCD 系列知識點[16487:1861258] image1---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.361714+0800 GCD 系列知識點[16487:1861258] image2---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.361844+0800 GCD 系列知識點[16487:1861258] image3---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.362145+0800 GCD 系列知識點[16487:1861258] image4---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.362262+0800 GCD 系列知識點[16487:1861258] image5---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.362374+0800 GCD 系列知識點[16487:1861258] image6---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.362489+0800 GCD 系列知識點[16487:1861258] image7---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.362604+0800 GCD 系列知識點[16487:1861258] image8---<NSThread: 0x60000006e140>{number = 1, name = main}
會發現和不同的for循環沒什么不同,都是在主線程中進行的。
注意:如果執行
dispatch_apply
的線程是串行隊列A的線程,同時執行dispatch_apply
時傳入queue
的也是串行隊列A,會發生死鎖。比如直接在主隊列中執行dispatch_apply
,同時傳入queue
也是主隊列,如:
//直接調用會出現死鎖
[self testApply];
-(void)testApply{
NSArray *imageArray = @[@"image1",@"image2",@"image3",@"image4",@"image5",@"image6",@"image7",@"image8"];
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_apply(imageArray.count, queue, ^(size_t index) {
NSLog(@"%@---%@",imageArray[index], [NSThread currentThread]);
});
}
當然這樣做也沒什么意義,一般
dispatch_apply
都是為了高效的異步的遍歷數組。所以一般dispatch_apply
傳入的queue
都是一個并發隊列,同時還把dispatch_apply
整體方法一個異步執行的并發隊列中,如:
NSArray *array = @[@"1", @"2", @"3", @"4", @"5", @"6"];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%zu : %@", index, [array objectAtIndex:index]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"currentThread = %@", [NSThread currentThread]);
NSLog(@"done");
});
});
5.6 信號量 dispatch_semaphore
干啥用的了,總結一句話:使用dispatch_semaphore 可以更好的控制并發多線程的任務處理。
//創建一個信號量,給他設置個信號量值
dispatch_semaphore_create(long value);
//可以使總信號量減1,當信號總量為0時就會一直等待,阻塞當前線程(當timeout 時間過后 也可以繼續執行),否則就可以正常執行。
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
//發送一個信號,使信號總量加1
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
看著也看不出來有啥用,下面舉例一些常見用法。
5.6.1 控制并發線程數量
下面來看看實際應用的例子:
現在我有一組圖片地址,我需要開辟一個并發隊列來下載這些圖片,等這些圖片都下載完了,告訴我我要去搞事情。
- (void)testSemaphoreSomething{
NSArray *imageArray = @[@"image1",@"image2",@"image3",@"image4",@"image5",@"image6",@"image7",@"image8",@"image8",@"image10"];
dispatch_queue_t dyConcurrent = dispatch_queue_create("dyConcurrent1", DISPATCH_QUEUE_CONCURRENT); //自己創建并行隊列
// dispatch_semaphore_t cacheSemphore = dispatch_semaphore_create(5);
for (NSString *oneImage in imageArray){
dispatch_async(dyConcurrent, ^{
// dispatch_semaphore_wait(cacheSemphore, DISPATCH_TIME_FOREVER);
NSLog(@"下載圖片:%@ 線程:%@",oneImage,[NSThread currentThread]);
sleep(1);
NSLog(@"下載完成");
// dispatch_semaphore_signal(cacheSemphore);
});
}
}
我們直接運行會發現會一下開辟imageArray.count
個線程來處理任務。
2018-02-27 18:22:11.014569+0800 GCD 系列知識點[1237:286459] 打印當前線程---<NSThread: 0x604000070200>{number = 1, name = main}
2018-02-27 18:22:11.014969+0800 GCD 系列知識點[1237:286648] 下載圖片:image5 線程:<NSThread: 0x604000272240>{number = 7, name = (null)}
2018-02-27 18:22:11.014974+0800 GCD 系列知識點[1237:286651] 下載圖片:image2 線程:<NSThread: 0x604000272140>{number = 4, name = (null)}
2018-02-27 18:22:11.015007+0800 GCD 系列知識點[1237:286647] 下載圖片:image4 線程:<NSThread: 0x604000272200>{number = 6, name = (null)}
2018-02-27 18:22:11.015007+0800 GCD 系列知識點[1237:286650] 下載圖片:image3 線程:<NSThread: 0x600000279080>{number = 5, name = (null)}
2018-02-27 18:22:11.015035+0800 GCD 系列知識點[1237:286649] 下載圖片:image1 線程:<NSThread: 0x604000272100>{number = 3, name = (null)}
2018-02-27 18:22:11.015389+0800 GCD 系列知識點[1237:286668] 下載圖片:image6 線程:<NSThread: 0x600000279100>{number = 8, name = (null)}
2018-02-27 18:22:11.015492+0800 GCD 系列知識點[1237:286669] 下載圖片:image7 線程:<NSThread: 0x600000279180>{number = 9, name = (null)}
2018-02-27 18:22:11.015589+0800 GCD 系列知識點[1237:286670] 下載圖片:image8 線程:<NSThread: 0x600000279200>{number = 10, name = (null)}
2018-02-27 18:22:11.015684+0800 GCD 系列知識點[1237:286671] 下載圖片:image9 線程:<NSThread: 0x600000279280>{number = 11, name = (null)}
2018-02-27 18:22:11.015764+0800 GCD 系列知識點[1237:286672] 下載圖片:image10 線程:<NSThread: 0x600000279300>{number = 12, name = (null)}
2018-02-27 18:22:12.017230+0800 GCD 系列知識點[1237:286651] 下載完成
2018-02-27 18:22:12.017232+0800 GCD 系列知識點[1237:286647] 下載完成
2018-02-27 18:22:12.017249+0800 GCD 系列知識點[1237:286648] 下載完成
2018-02-27 18:22:12.017253+0800 GCD 系列知識點[1237:286650] 下載完成
2018-02-27 18:22:12.017292+0800 GCD 系列知識點[1237:286649] 下載完成
2018-02-27 18:22:12.018566+0800 GCD 系列知識點[1237:286668] 下載完成
2018-02-27 18:22:12.023732+0800 GCD 系列知識點[1237:286671] 下載完成
2018-02-27 18:22:12.023728+0800 GCD 系列知識點[1237:286670] 下載完成
2018-02-27 18:22:12.023732+0800 GCD 系列知識點[1237:286669] 下載完成
2018-02-27 18:22:12.023761+0800 GCD 系列知識點[1237:286672] 下載完成
假如這里imageArray.count
是100多,很顯然,我們這樣的暴力處理是不切合實際的。使用 Semaphore我們可以控制同時并發的線程數量。我們打開屏蔽代碼,看輸出結果:
2018-02-27 18:25:25.741325+0800 GCD 系列知識點[1265:295325] 打印當前線程---<NSThread: 0x60400006e800>{number = 1, name = main}
2018-02-27 18:25:25.741625+0800 GCD 系列知識點[1265:295509] 下載圖片:image2 線程:<NSThread: 0x60000027b380>{number = 4, name = (null)}
2018-02-27 18:25:25.741630+0800 GCD 系列知識點[1265:295508] 下載圖片:image1 線程:<NSThread: 0x60400026fac0>{number = 3, name = (null)}
2018-02-27 18:25:25.741637+0800 GCD 系列知識點[1265:295510] 下載圖片:image3 線程:<NSThread: 0x60000027b280>{number = 5, name = (null)}
2018-02-27 18:25:25.741677+0800 GCD 系列知識點[1265:295511] 下載圖片:image4 線程:<NSThread: 0x60000027b240>{number = 6, name = (null)}
2018-02-27 18:25:25.742019+0800 GCD 系列知識點[1265:295525] 下載圖片:image6 線程:<NSThread: 0x60400026fd00>{number = 7, name = (null)}
2018-02-27 18:25:26.742404+0800 GCD 系列知識點[1265:295509] 下載完成
2018-02-27 18:25:26.742442+0800 GCD 系列知識點[1265:295508] 下載完成
2018-02-27 18:25:26.742871+0800 GCD 系列知識點[1265:295525] 下載完成
2018-02-27 18:25:26.742871+0800 GCD 系列知識點[1265:295511] 下載完成
2018-02-27 18:25:26.742907+0800 GCD 系列知識點[1265:295510] 下載完成
2018-02-27 18:25:26.742965+0800 GCD 系列知識點[1265:295524] 下載圖片:image5 線程:<NSThread: 0x604000270080>{number = 8, name = (null)}
2018-02-27 18:25:26.743013+0800 GCD 系列知識點[1265:295526] 下載圖片:image7 線程:<NSThread: 0x60000027c080>{number = 9, name = (null)}
2018-02-27 18:25:26.743472+0800 GCD 系列知識點[1265:295527] 下載圖片:image8 線程:<NSThread: 0x60000027c700>{number = 10, name = (null)}
2018-02-27 18:25:26.743472+0800 GCD 系列知識點[1265:295528] 下載圖片:image9 線程:<NSThread: 0x60000027c640>{number = 11, name = (null)}
2018-02-27 18:25:26.743546+0800 GCD 系列知識點[1265:295529] 下載圖片:image10 線程:<NSThread: 0x60400026ac80>{number = 12, name = (null)}
2018-02-27 18:25:27.746854+0800 GCD 系列知識點[1265:295528] 下載完成
2018-02-27 18:25:27.746912+0800 GCD 系列知識點[1265:295527] 下載完成
2018-02-27 18:25:27.746855+0800 GCD 系列知識點[1265:295524] 下載完成
2018-02-27 18:25:27.746954+0800 GCD 系列知識點[1265:295526] 下載完成
2018-02-27 18:25:27.746981+0800 GCD 系列知識點[1265:295529] 下載完成
會發現我們先開辟 5 個線程去下載,5個都下載完成了后再開辟5個去搞,這樣就可以減輕系統并發的數量。
看結果你是不是感覺這有點像dispatch_barrier_async
都是把任務拆成幾部分一塊一塊執行,但是他們是有本質不同的:dispatch_barrier_async
控制的是任務,它操作的對象是任務分塊,不會處理線程的多少,而Semaphore控制同時并發的線程數量,再由一定的線程數量去執行任務。這好比同樣是蓋樓要10個人,barrier
類似于我把樓分成2部分,我們10個人先蓋第一部分,第一部分完了再蓋第二部分。而Semaphore
是我們同時只讓5個人蓋樓,剩下的休息,誰干完了誰休息,那個之前在休息的上來干。
那如果dispatch_semaphore_create(5)
改成dispatch_semaphore_create(1)
會怎么樣,改完之后運行。
2018-02-27 18:29:33.880314+0800 GCD 系列知識點[1322:310027] 打印當前線程---<NSThread: 0x604000077200>{number = 1, name = main}
2018-02-27 18:29:33.880623+0800 GCD 系列知識點[1322:310152] 下載圖片:image1 線程:<NSThread: 0x604000478c40>{number = 3, name = (null)}
2018-02-27 18:29:34.881307+0800 GCD 系列知識點[1322:310152] 下載完成
2018-02-27 18:29:34.881755+0800 GCD 系列知識點[1322:310155] 下載圖片:image3 線程:<NSThread: 0x60400046f280>{number = 4, name = (null)}
2018-02-27 18:29:35.887236+0800 GCD 系列知識點[1322:310155] 下載完成
2018-02-27 18:29:35.887694+0800 GCD 系列知識點[1322:310153] 下載圖片:image2 線程:<NSThread: 0x604000479540>{number = 5, name = (null)}
2018-02-27 18:29:36.891046+0800 GCD 系列知識點[1322:310153] 下載完成
2018-02-27 18:29:36.891503+0800 GCD 系列知識點[1322:310154] 下載圖片:image4 線程:<NSThread: 0x60000007a380>{number = 6, name = (null)}
2018-02-27 18:29:37.894212+0800 GCD 系列知識點[1322:310154] 下載完成
2018-02-27 18:29:37.894732+0800 GCD 系列知識點[1322:310164] 下載圖片:image5 線程:<NSThread: 0x600000263280>{number = 7, name = (null)}
2018-02-27 18:29:38.900181+0800 GCD 系列知識點[1322:310164] 下載完成
2018-02-27 18:29:38.900712+0800 GCD 系列知識點[1322:310166] 下載圖片:image7 線程:<NSThread: 0x604000479740>{number = 8, name = (null)}
2018-02-27 18:29:39.904618+0800 GCD 系列知識點[1322:310166] 下載完成
2018-02-27 18:29:39.905013+0800 GCD 系列知識點[1322:310165] 下載圖片:image6 線程:<NSThread: 0x600000263580>{number = 9, name = (null)}
2018-02-27 18:29:40.908401+0800 GCD 系列知識點[1322:310165] 下載完成
2018-02-27 18:29:40.908814+0800 GCD 系列知識點[1322:310168] 下載圖片:image9 線程:<NSThread: 0x6000002631c0>{number = 10, name = (null)}
2018-02-27 18:29:41.910161+0800 GCD 系列知識點[1322:310168] 下載完成
2018-02-27 18:29:41.910634+0800 GCD 系列知識點[1322:310167] 下載圖片:image8 線程:<NSThread: 0x6040004794c0>{number = 11, name = (null)}
2018-02-27 18:29:42.915150+0800 GCD 系列知識點[1322:310167] 下載完成
2018-02-27 18:29:42.915664+0800 GCD 系列知識點[1322:310169] 下載圖片:image10 線程:<NSThread: 0x604000471980>{number = 12, name = (null)}
2018-02-27 18:29:43.919164+0800 GCD 系列知識點[1322:310169] 下載完成
會發現調用的是并發隊列,但是限制信號量總數為1 后,并發隊列就變成了串行隊列。
5.6.2 對多線程并發執行任務共享資源進行加鎖處理
案例:總共有20張火車票,有兩個售賣火車票的窗口,一個是北京火車票售賣窗口,另一個是上海火車票售賣窗口。兩個窗口同時售賣火車票,賣完為止。
這里的問題核心是,兩個窗口同時買票,在賣出一張票后總票數都會減1,如何確保票不多賣或賣叉?這就相當于計算機中的多條線程同時寫一個資源,可能會出現混亂的問題。看Demo
-(void)testSaleTicketSemaphoreLock{
self.allTicket = 20;
//開辟兩個線程來買票
NSThread *oneThread = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
oneThread.name = @"北京";
NSThread *twoThread = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
twoThread.name = @"上海";
[oneThread start];
[twoThread start];
}
-(void)saleTicket{
while (1) {
if (self.allTicket > 0){
self.allTicket --;
NSLog(@"剩余票數:%ld 當前窗口線程:%@",(long)self.allTicket,[NSThread currentThread].name);
[NSThread sleepForTimeInterval:1];
}else{
NSLog(@"所有票都買完了");
break;
}
}
}
2018-02-28 11:05:45.911240+0800 GCD 系列知識點[11745:1027862] 打印當前線程---<NSThread: 0x600000068140>{number = 1, name = main}
2018-02-28 11:05:45.911688+0800 GCD 系列知識點[11745:1028003] 剩余票數:19 當前窗口線程:北京
2018-02-28 11:05:45.911697+0800 GCD 系列知識點[11745:1028004] 剩余票數:18 當前窗口線程:上海
2018-02-28 11:05:46.916487+0800 GCD 系列知識點[11745:1028004] 剩余票數:17 當前窗口線程:上海
2018-02-28 11:05:46.916492+0800 GCD 系列知識點[11745:1028003] 剩余票數:16 當前窗口線程:北京
2018-02-28 11:05:47.922052+0800 GCD 系列知識點[11745:1028003] 剩余票數:15 當前窗口線程:北京
2018-02-28 11:05:47.922066+0800 GCD 系列知識點[11745:1028004] 剩余票數:14 當前窗口線程:上海
2018-02-28 11:05:48.925762+0800 GCD 系列知識點[11745:1028003] 剩余票數:13 當前窗口線程:北京
2018-02-28 11:05:48.925762+0800 GCD 系列知識點[11745:1028004] 剩余票數:12 當前窗口線程:上海
2018-02-28 11:05:49.930191+0800 GCD 系列知識點[11745:1028004] 剩余票數:11 當前窗口線程:上海
2018-02-28 11:05:49.930229+0800 GCD 系列知識點[11745:1028003] 剩余票數:11 當前窗口線程:北京
2018-02-28 11:05:50.934367+0800 GCD 系列知識點[11745:1028003] 剩余票數:10 當前窗口線程:北京
2018-02-28 11:05:50.934473+0800 GCD 系列知識點[11745:1028004] 剩余票數:9 當前窗口線程:上海
2018-02-28 11:05:51.937956+0800 GCD 系列知識點[11745:1028003] 剩余票數:8 當前窗口線程:北京
2018-02-28 11:05:51.937956+0800 GCD 系列知識點[11745:1028004] 剩余票數:8 當前窗口線程:上海
2018-02-28 11:05:52.942655+0800 GCD 系列知識點[11745:1028004] 剩余票數:7 當前窗口線程:上海
2018-02-28 11:05:52.942655+0800 GCD 系列知識點[11745:1028003] 剩余票數:7 當前窗口線程:北京
2018-02-28 11:05:53.945901+0800 GCD 系列知識點[11745:1028004] 剩余票數:6 當前窗口線程:上海
2018-02-28 11:05:53.945901+0800 GCD 系列知識點[11745:1028003] 剩余票數:6 當前窗口線程:北京
2018-02-28 11:05:54.949424+0800 GCD 系列知識點[11745:1028003] 剩余票數:5 當前窗口線程:北京
2018-02-28 11:05:54.949435+0800 GCD 系列知識點[11745:1028004] 剩余票數:4 當前窗口線程:上海
2018-02-28 11:05:55.952903+0800 GCD 系列知識點[11745:1028003] 剩余票數:3 當前窗口線程:北京
2018-02-28 11:05:55.952904+0800 GCD 系列知識點[11745:1028004] 剩余票數:2 當前窗口線程:上海
2018-02-28 11:05:56.956317+0800 GCD 系列知識點[11745:1028004] 剩余票數:1 當前窗口線程:上海
2018-02-28 11:05:56.956321+0800 GCD 系列知識點[11745:1028003] 剩余票數:0 當前窗口線程:北京
2018-02-28 11:05:57.961572+0800 GCD 系列知識點[11745:1028003] 所有票都買完了
2018-02-28 11:05:57.961599+0800 GCD 系列知識點[11745:1028004] 所有票都買完了
觀察會發現余票在 11、8 、7、6的時候揣想那混亂,多賣了票。這就是多線程訪問公共資源出現不同步的問題。我們使用dispatch_semaphore 來加鎖處理一下,看看結果怎么樣?
-(void)testSaleTicketSemaphoreLock{
self.allTicket = 20;
self.ticketSemaphore = dispatch_semaphore_create(1);
//開辟兩個線程來買票
NSThread *oneThread = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
oneThread.name = @"北京";
NSThread *twoThread = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
twoThread.name = @"上海";
[oneThread start];
[twoThread start];
}
-(void)saleTicket{
while (1) {
//加鎖 如果信號量>0 執行wait 信號量減1 下方代碼執行,如果信號量<0 那就不執行下方代碼 阻塞當前
dispatch_semaphore_wait(self.ticketSemaphore, DISPATCH_TIME_FOREVER);
if (self.allTicket > 0){
self.allTicket --;
NSLog(@"剩余票數:%ld 當前窗口線程:%@",(long)self.allTicket,[NSThread currentThread].name);
[NSThread sleepForTimeInterval:1];
}else{
NSLog(@"所有票都買完了");
dispatch_semaphore_signal(self.ticketSemaphore);
break;
}
//解鎖 執行signal 信號量+1
dispatch_semaphore_signal(self.ticketSemaphore);
}
}
2018-02-28 14:17:28.118236+0800 GCD 系列知識點[13342:1195687] 打印當前線程---<NSThread: 0x604000068fc0>{number = 1, name = main}
2018-02-28 14:17:28.120479+0800 GCD 系列知識點[13342:1195906] 剩余票數:19 當前窗口線程:北京
2018-02-28 14:17:29.124787+0800 GCD 系列知識點[13342:1195907] 剩余票數:18 當前窗口線程:上海
2018-02-28 14:17:30.127421+0800 GCD 系列知識點[13342:1195906] 剩余票數:17 當前窗口線程:北京
2018-02-28 14:17:31.130508+0800 GCD 系列知識點[13342:1195907] 剩余票數:16 當前窗口線程:上海
2018-02-28 14:17:32.133827+0800 GCD 系列知識點[13342:1195906] 剩余票數:15 當前窗口線程:北京
2018-02-28 14:17:33.138644+0800 GCD 系列知識點[13342:1195907] 剩余票數:14 當前窗口線程:上海
2018-02-28 14:17:34.142570+0800 GCD 系列知識點[13342:1195906] 剩余票數:13 當前窗口線程:北京
2018-02-28 14:17:35.143104+0800 GCD 系列知識點[13342:1195907] 剩余票數:12 當前窗口線程:上海
2018-02-28 14:17:36.144129+0800 GCD 系列知識點[13342:1195906] 剩余票數:11 當前窗口線程:北京
2018-02-28 14:17:37.149313+0800 GCD 系列知識點[13342:1195907] 剩余票數:10 當前窗口線程:上海
2018-02-28 14:17:38.154850+0800 GCD 系列知識點[13342:1195906] 剩余票數:9 當前窗口線程:北京
2018-02-28 14:17:39.160380+0800 GCD 系列知識點[13342:1195907] 剩余票數:8 當前窗口線程:上海
2018-02-28 14:17:40.165849+0800 GCD 系列知識點[13342:1195906] 剩余票數:7 當前窗口線程:北京
2018-02-28 14:17:41.167534+0800 GCD 系列知識點[13342:1195907] 剩余票數:6 當前窗口線程:上海
2018-02-28 14:17:42.173125+0800 GCD 系列知識點[13342:1195906] 剩余票數:5 當前窗口線程:北京
2018-02-28 14:17:43.177330+0800 GCD 系列知識點[13342:1195907] 剩余票數:4 當前窗口線程:上海
2018-02-28 14:17:44.181994+0800 GCD 系列知識點[13342:1195906] 剩余票數:3 當前窗口線程:北京
2018-02-28 14:17:45.183557+0800 GCD 系列知識點[13342:1195907] 剩余票數:2 當前窗口線程:上海
2018-02-28 14:17:46.189193+0800 GCD 系列知識點[13342:1195906] 剩余票數:1 當前窗口線程:北京
2018-02-28 14:17:47.194826+0800 GCD 系列知識點[13342:1195907] 剩余票數:0 當前窗口線程:上海
2018-02-28 14:17:48.195913+0800 GCD 系列知識點[13342:1195906] 所有票都買完了
2018-02-28 14:17:48.196297+0800 GCD 系列知識點[13342:1195907] 所有票都買完了
會發現總剩余票數沒有發生錯位。其實dispatch_semaphore這里就是控制了多線程處理任務 并發時機,也是5.1.1的一種特殊形式。
5.6.3 頁面有多個網絡請求都完成后才處理任務
上面說的使用dispatch_group_enter
和dispatch_group_leave
可以實現多網絡請求完成監聽處理。我們使用dispatch_semaphore也可以完成這種操作,具體直接上下面那段偽代碼
dispatch_group_t group = dispatch_group_create();
//網絡請求1
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//創建一個為0信號量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[異步網絡請求1:{
//成功或失敗給信號加1
成功Block:dispatch_semaphore_signal(semaphore);
失敗Block:dispatch_semaphore_signal(semaphore);
}];
//只有信號量>1 下面代碼才走,方法才算執行完畢
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
//網絡請求2
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//創建一個為0信號量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[異步網絡請求2:{
//成功或失敗給信號加1
成功Block:dispatch_semaphore_signal(semaphore);
失敗Block:dispatch_semaphore_signal(semaphore);
}];
//只有信號量>1 下面代碼才走,方法才算執行完畢
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
//網絡請求3
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//創建一個為0信號量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[異步網絡請求3:{
//成功或失敗給信號加1
成功Block:dispatch_semaphore_signal(semaphore);
失敗Block:dispatch_semaphore_signal(semaphore);
}];
//只有信號量>1 下面代碼才走,方法才算執行完畢
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有網絡請求完畢");
})
這樣的組合就能實現多網絡請求都有響應后的網絡回調了!
補充: 那么如何讓這些網絡請求有個先后順序了,就是說請求1返回回來的參數我要用到請求2上,這樣的需求也很常見,用到的知識就不是GCD的了,而是NSOperation上的知識了,從別人博客弄來的代碼,直接放上:
//按照順序
NSBlockOperation *operation_1 = [NSBlockOperation blockOperationWithBlock:^{
[self request1];
}];
NSBlockOperation *operation_2 = [NSBlockOperation blockOperationWithBlock:^{
[self request2];
}];
NSBlockOperation *operation_3 = [NSBlockOperation blockOperationWithBlock:^{
[self request3];
}];
//設置依賴
[operation_2 addDependency:operation_1];
[operation_3 addDependency:operation_1];
//創建隊列并添加任務
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperations:@[operation_3,operation_2,operation_1] waitUntilFinished:YES];
其中的[self request]
里面代碼還是使用信號量來控制網絡請求真正結束,拿request1代碼舉個栗子:
-(void)request1{
//創建一個為0信號量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[異步網絡請求3:{
//成功或失敗給信號加1
成功Block:dispatch_semaphore_signal(semaphore);
失敗Block:dispatch_semaphore_signal(semaphore);
}];
//只有信號量>1 下面代碼才走,方法才算執行完畢
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
以上都是我根據別人的博客和自己代碼實踐所得,如有冒犯和不足歡迎大家批評。
感覺在微小簡單的東西,如果系統學習起來,還是有盲點的,所以說所有的事情態度要端正,虛心點吧
引用博客
http://www.lxweimin.com/p/2d57c72016c6
http://blog.csdn.net/Cloudox_/article/details/71107179
http://www.lxweimin.com/p/228403206664
https://www.cnblogs.com/zhou--fei/p/6747938.html
http://www.lxweimin.com/p/aa3cfcabb470