多線程開發(fā)是日常開發(fā)任務(wù)中不可缺少的一部分鸠蚪,在iOS開發(fā)中常用到的多線程開發(fā)技術(shù)有
GCD襟衰、NSOperation楔壤、NSThread
拷况,本文主要講解多線系列文章中關(guān)于NSOperation
的相關(guān)知識和使用詳解。
1梗顺、GCD簡介
GCD
對于iOS開發(fā)者來說并不陌生泡孩,在實際開發(fā)中我們會經(jīng)常用到GCD進行多線程的處理,那么GCD是什么呢寺谤?
Grand Central Dispatch(GCD) 是 Apple 開發(fā)的一個多核編程的較新的解決方法仑鸥。它主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對稱多處理系統(tǒng)。它是一個在線程池模式的基礎(chǔ)上執(zhí)行的并發(fā)任務(wù)变屁。在 Mac OS X 10.6 雪豹中首次推出眼俊,也可在 iOS 4 及以上版本使用。
GCD有著很明顯的優(yōu)勢粟关,正是這些優(yōu)勢才使得GCD在處理多線程問題有著舉足輕重的地位疮胖。
- GCD是apple為多核的并行運算提出的解決方案。
- GCD能較好的利用CPU內(nèi)核資源。
- GCD不需要開發(fā)者去管理線程的生命周期澎灸。
- 使用簡便谷市,開發(fā)者只需要告訴GCD執(zhí)行什么任務(wù),并不需要編寫任何線程管理代碼击孩。
2、GCD任務(wù)和隊列
相信很多初級開發(fā)者會對GCD任務(wù)和隊列之間的關(guān)系理解含糊不清鹏漆,實際上隊列只是提供了保存任務(wù)的容器巩梢。為了更好的理解GCD,很有必要先了解任務(wù)和隊列的概念艺玲。
2.1 GCD任務(wù)
任務(wù)就是需要執(zhí)行的操作括蝠,是GCD中放在block中在線程中執(zhí)行的那段代碼。任務(wù)的執(zhí)行的方式有同步執(zhí)行和異步執(zhí)行兩中執(zhí)行方式饭聚。兩者的主要區(qū)別是是否等待隊列的任務(wù)執(zhí)行結(jié)束忌警,以及是否具備開啟新線程的能力。
- 同步執(zhí)行(sync):同步添加任務(wù)到隊列中秒梳,在隊列之前的任務(wù)執(zhí)行結(jié)束之前會一直等待法绵;同步執(zhí)行的任務(wù)只能在當(dāng)前線程中執(zhí)行,不具備開啟新線程的能力酪碘。
- 異步執(zhí)行(async):異步添加任務(wù)到隊列中朋譬,并需要理會隊列中其他的任務(wù),添加即執(zhí)行兴垦;異步執(zhí)行可以在新的線程中執(zhí)行徙赢,具備開啟新的線程的能力。
2.2 GCD的隊列
隊列:隊列是一種特殊的線性表探越,隊列中允許插入操作的一端稱為隊尾狡赐,允許刪除操作的一端稱為隊頭,是一種先進先出的結(jié)構(gòu)钦幔。在GCD里面隊列是指執(zhí)行任務(wù)的等待隊列枕屉,是用來存放任務(wù)的。按照隊列的結(jié)構(gòu)特性鲤氢,新任務(wù)總是插入在隊列的末尾搀庶,而任務(wù)的執(zhí)行總是從隊列的對頭輸出,每讀取一個任務(wù)铜异,則從隊列中釋放一個任務(wù)哥倔。GCD的隊列分為串行隊列和并發(fā)隊列兩種,兩者都符合 FIFO(先進先出)的原則揍庄。兩者的主要區(qū)別是:執(zhí)行順序不同咆蒿,以及開啟線程數(shù)不同。
- 串行隊列:只開啟一個線程,每次只能有一個任務(wù)執(zhí)行沃测,等待執(zhí)行完畢后才會執(zhí)行下一個任務(wù)缭黔。
- 并發(fā)隊列:可以讓對個任務(wù)同時執(zhí)行,也就是開啟多個線程蒂破,讓多個任務(wù)同時執(zhí)行馏谨。
兩者之間區(qū)別如下圖所示:
3、GCD基本使用
GCD的使用很簡單附迷,首先創(chuàng)建一個隊列惧互,然后向隊列中追加任務(wù),系統(tǒng)會根據(jù)任務(wù)的類型執(zhí)行任務(wù)喇伯。
3.1喊儡、隊列的創(chuàng)建
- 隊列的創(chuàng)建很簡單,只需要調(diào)用
dispatch_queue_create
方法傳入相對應(yīng)的參數(shù)便可稻据。這個方法有兩個參數(shù):
- 第一個參數(shù)表示隊列的唯一標(biāo)識艾猜,可以傳空。
- 第二個參數(shù)用來識別是串行隊列還是并發(fā)隊列捻悯。
DISPATCH_QUEUE_SERIAL
表示串行隊列匆赃,DISPATCH_QUEUE_CONCURRENT
表示并發(fā)隊列。
// 創(chuàng)建串行隊列
dispatch_queue_t queue = dispatch_queue_create("DS.GCDDemo", DISPATCH_QUEUE_SERIAL);
// 創(chuàng)建并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("DS.GCDDemo", DISPATCH_QUEUE_CONCURRENT);
- GCD默認(rèn)提供一種全局并發(fā)隊列今缚,調(diào)用
dispatch_get_global_queue
方法來獲取全局并發(fā)隊列炸庞。這個方法需要傳入兩個參數(shù)。
- 第一個參數(shù)是一個長整型類型的參數(shù)荚斯,表示隊列優(yōu)先級埠居,有
DISPATCH_QUEUE_PRIORITY_HIGH
、DISPATCH_QUEUE_PRIORITY_LOW
事期、DISPATCH_QUEUE_PRIORITY_BACKGROUND
滥壕、DISPATCH_QUEUE_PRIORITY_DEFAULT
四個選項,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT
兽泣。 - 第二個參數(shù)暫時沒用绎橘,用 0 即可。
// 獲取全局并發(fā)隊列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- GCD默認(rèn)提供了主隊列唠倦,調(diào)用
dispatch_get_main_queue
方法獲取称鳞,所有放在主隊列中的任務(wù)都會在主線程中執(zhí)行。主隊列是一種串行隊列稠鼻。
// 主隊
dispatch_queue_t mainQueue = dispatch_get_main_queue();
3.2冈止、創(chuàng)建任務(wù)
GCD調(diào)用dispatch_sync
創(chuàng)建同步任務(wù),調(diào)用dispatch_async
創(chuàng)建異步任務(wù)候齿。任務(wù)的內(nèi)容都是在block代碼塊中熙暴。
//異步任務(wù)
dispatch_async(queue, ^{
//異步執(zhí)行的代碼
});
//同步任務(wù)
dispatch_sync(queue, ^{
//同步執(zhí)行的代碼
});
3.3闺属、任務(wù)和隊列的組合
創(chuàng)建的任務(wù)需要放在隊列中去執(zhí)行,同時考慮到主隊列的特殊性周霉,那么在不考慮嵌套任務(wù)的情況下就會存在同步任務(wù)+串行隊列掂器、同步任務(wù)+并發(fā)隊列、異步任務(wù)+串行隊列俱箱、異步任務(wù)+并發(fā)隊列国瓮、主隊列+同步任務(wù)、主隊列+異步任務(wù)六種組合狞谱,下面我們來分析下這幾種組合乃摹。
- 同步任務(wù)+串行隊列:同步任務(wù)不會開啟新的線程,任務(wù)串行執(zhí)行芋簿。
- 同步任務(wù)+并發(fā)隊列:同步任務(wù)不會開啟新的線程,雖然任務(wù)在并發(fā)隊列中璃饱,但是系統(tǒng)只默認(rèn)開啟了一個主線程与斤,沒有開啟子線程,所以任務(wù)串行執(zhí)行荚恶。
- 異步任務(wù)+串行隊列:異步任務(wù)有開啟新的線程撩穿,任務(wù)串行執(zhí)行。
- 異步任務(wù)+并發(fā)隊列:異步任務(wù)有開啟新的線程谒撼,任務(wù)并發(fā)執(zhí)行食寡。
- 主隊列+同步任務(wù):主隊列是一種串行隊列,任務(wù)在主線程中串行執(zhí)行廓潜,將同步任務(wù)添加到主隊列中會造成追加的同步任務(wù)和主線程中的任務(wù)相互等待阻塞主線程抵皱,導(dǎo)致死鎖。
- 主隊列+異步任務(wù):主隊列是一種串行隊列辩蛋,任務(wù)在主線程中串行執(zhí)行呻畸,即使是追加的異步任務(wù)也不會開啟新的線程,任務(wù)串行執(zhí)行悼院。
下面我們來看看各種組合之間的使用伤为。
3.4、GCD的基礎(chǔ)使用
3.4.1据途、同步任務(wù)+串行隊列
- (void)syncTaskWithSerial {
NSLog(@"currentThread:%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"currentThread-1:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"currentThread-2:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"currentThread-3:%@", [NSThread currentThread]);
});
}
打印結(jié)果:
2020-03-12 21:34:25.807965+0800 ThreadDemo[51144:6948582] currentThread:<NSThread: 0x600001739100>{number = 1, name = main}
2020-03-12 21:34:25.808231+0800 ThreadDemo[51144:6948582] currentThread-1:<NSThread: 0x600001739100>{number = 1, name = main}
2020-03-12 21:34:25.808467+0800 ThreadDemo[51144:6948582] currentThread-2:<NSThread: 0x600001739100>{number = 1, name = main}
2020-03-12 21:34:25.808669+0800 ThreadDemo[51144:6948582] currentThread-3:<NSThread: 0x600001739100>{number = 1, name = main}
從上面代碼運行的結(jié)果可以看出绞愚,并沒有開啟新的線程,任務(wù)是按順序執(zhí)行的颖医。
3.4.2位衩、同步任務(wù)+并發(fā)隊列
- (void)syncTaskWithConcurrent {
NSLog(@"current thread:%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"current thread-1:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"current thread-2:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"current thread-3:%@", [NSThread currentThread]);
});
}
打印結(jié)果:
2020-03-12 21:39:45.931001+0800 ThreadDemo[51225:6953218] current thread:<NSThread: 0x600002abe0c0>{number = 1, name = main}
2020-03-12 21:39:45.931259+0800 ThreadDemo[51225:6953218] current thread-1:<NSThread: 0x600002abe0c0>{number = 1, name = main}
2020-03-12 21:39:45.931442+0800 ThreadDemo[51225:6953218] current thread-2:<NSThread: 0x600002abe0c0>{number = 1, name = main}
2020-03-12 21:39:45.931606+0800 ThreadDemo[51225:6953218] current thread-3:<NSThread: 0x600002abe0c0>{number = 1, name = main}
從上面代碼運行的結(jié)果可以看出,同步任務(wù)不會開啟新的線程熔萧,雖然任務(wù)在并發(fā)隊列中蚂四,但是系統(tǒng)只默認(rèn)開啟了一個主線程光戈,沒有開啟子線程,所以任務(wù)串行執(zhí)行遂赠。
3.4.3久妆、異步任務(wù)+串行隊列
- (void)asyncTaskWithSeria{
NSLog(@"current thread:%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"current thread-1:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"current thread-2:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"current thread-3:%@", [NSThread currentThread]);
});
NSLog(@"4");
}
打印結(jié)果:
2020-03-12 21:44:22.369058+0800 ThreadDemo[51283:6957598] current thread:<NSThread: 0x6000031891c0>{number = 1, name = main}
2020-03-12 21:44:22.369279+0800 ThreadDemo[51283:6957598] 4
2020-03-12 21:44:22.369346+0800 ThreadDemo[51283:6958684] current thread-1:<NSThread: 0x6000031acb80>{number = 7, name = (null)}
2020-03-12 21:44:22.369511+0800 ThreadDemo[51283:6958684] current thread-2:<NSThread: 0x6000031acb80>{number = 7, name = (null)}
2020-03-12 21:44:22.369675+0800 ThreadDemo[51283:6958684] current thread-3:<NSThread: 0x6000031acb80>{number = 7, name = (null)}
從上面代碼運行的結(jié)果可以看出,開啟了一個新的線程跷睦,說明異步任務(wù)具備開啟新的線程的能力筷弦,但是由于任務(wù)是在串行隊列中執(zhí)行的,所以任務(wù)是順序執(zhí)行的抑诸。
3.4.4烂琴、異步任務(wù)+并發(fā)隊列
- (void)asyncTaskWithConcurrent{
NSLog(@"current thread:%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"current thread-1:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"current thread-2:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"current thread-3:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"current thread-4:%@", [NSThread currentThread]);
});
}
打印結(jié)果:
2020-03-12 21:59:36.511770+0800 ThreadDemo[51635:6976397] current thread:<NSThread: 0x60000024ed00>{number = 1, name = main}
2020-03-12 21:59:36.512015+0800 ThreadDemo[51635:6976575] current thread-2:<NSThread: 0x600000214ec0>{number = 5, name = (null)}
2020-03-12 21:59:36.512011+0800 ThreadDemo[51635:6976577] current thread-1:<NSThread: 0x600000215700>{number = 4, name = (null)}
2020-03-12 21:59:36.512028+0800 ThreadDemo[51635:6976580] current thread-3:<NSThread: 0x60000021f2c0>{number = 6, name = (null)}
2020-03-12 21:59:36.512035+0800 ThreadDemo[51635:6976578] current thread-4:<NSThread: 0x60000023b340>{number = 7, name = (null)}
從上面代碼的運行結(jié)果可以看出,生成了多個線程蜕乡,并且任務(wù)是隨機執(zhí)行(并發(fā)執(zhí)行)的奸绷。
3.4.5、主隊列+同步任務(wù)
-(void)syncTaskWithMain{
NSLog(@"currentThread---%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
});
NSLog(@"4");
}
打印結(jié)果:
2020-03-12 22:05:01.689594+0800 ThreadDemo[51754:6982402] currentThread---<NSThread: 0x600003eaed00>{number = 1, name = main}
(lldb)
很明顯上面這段代碼運行崩潰了层玲,這是因為我們在主線程中執(zhí)行 syncTaskWithMain
方法号醉,相當(dāng)于把 syncTaskWithMain
任務(wù)放到了主線程的隊列中。而 同步執(zhí)行
會等待當(dāng)前隊列中的任務(wù)執(zhí)行完畢辛块,才會接著執(zhí)行畔派。那么當(dāng)我們把 任務(wù) 1
追加到主隊列中,任務(wù) 1
就在等待主線程處理完 syncTaskWithMain
任務(wù)润绵。而syncMain
任務(wù)需要等待任務(wù) 1
執(zhí)行完畢线椰,這樣就形成了相互等待的情況,產(chǎn)生了死鎖尘盼。
3.4.6憨愉、主隊列+異步任務(wù)
-(void)asyncTaskWithMain{
NSLog(@"currentThread---%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
});
NSLog(@"4");
}
打印結(jié)果:
2020-03-12 22:09:49.285203+0800 ThreadDemo[51832:6986908] currentThread---<NSThread: 0x600000aff3c0>{number = 1, name = main}
2020-03-12 22:09:49.285539+0800 ThreadDemo[51832:6986908] 4
2020-03-12 22:09:49.326310+0800 ThreadDemo[51832:6986908] 1---<NSThread: 0x600000aff3c0>{number = 1, name = main}
2020-03-12 22:09:49.326749+0800 ThreadDemo[51832:6986908] 2---<NSThread: 0x600000aff3c0>{number = 1, name = main}
2020-03-12 22:09:49.326988+0800 ThreadDemo[51832:6986908] 3---<NSThread: 0x600000aff3c0>{number = 1, name = main}
從上面代碼的運行結(jié)果可以看出,雖然是異步任務(wù)卿捎,但是并沒有開啟新的線程莱衩,任然是在主線程中執(zhí)行,并且任務(wù)是順序執(zhí)行的娇澎。
3.5笨蚁、任務(wù)嵌套使用
關(guān)于任務(wù)的嵌套使用有多種情況,這里做一個簡單的總結(jié)趟庄,
區(qū)別 | 『異步執(zhí)行+并發(fā)隊列』嵌套『同一個并發(fā)隊列』 | 『同步執(zhí)行+并發(fā)隊列』嵌套『同一個并發(fā)隊列』 | 『異步執(zhí)行+串行隊列』嵌套『同一個串行隊列』 | 『同步執(zhí)行+串行隊列』嵌套『同一個串行隊列』 |
---|---|---|---|---|
同步 | 沒有開啟新的線程括细,串行執(zhí)行任務(wù) | 沒有開啟新線程,串行執(zhí)行任務(wù) | 死鎖卡住不執(zhí)行 | 死鎖卡住不執(zhí)行 |
異步 | 有開啟新線程戚啥,并發(fā)執(zhí)行任務(wù) | 有開啟新線程奋单,并發(fā)執(zhí)行任務(wù) | 有開啟新線程(1 條),串行執(zhí)行任務(wù) | 有開啟新線程(1 條)猫十,串行執(zhí)行任務(wù) |
對于『異步執(zhí)行+串行隊列』嵌套『同一個串行隊列』造成死鎖的情況請看如下代碼:
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
//異步任務(wù)A
dispatch_async(queue, ^{
//同步任務(wù)B
dispatch_sync(queue, ^{
NSLog(@"任務(wù)C---%@",[NSThread currentThread]);
});
});
首先異步任務(wù)A進入到隊列中览濒,同步任務(wù)B對于異步任務(wù)A來說是代碼執(zhí)行部分呆盖,C對于同步任務(wù)B來說是代碼執(zhí)行部分,因為是在串行隊列中贷笛,任務(wù)是串行執(zhí)行的应又,根據(jù)隊列先進先出原則,首先需要把任務(wù)A取出執(zhí)行乏苦,即執(zhí)行B的部分株扛,但是B依賴C的執(zhí)行,而C等待著B執(zhí)行完成后執(zhí)行汇荐,這樣就形成了一個相互等待洞就,造成死鎖卡死。
同步執(zhí)行+串行隊列』嵌套『同一個串行隊列』造成死鎖的情況同理分析掀淘。
4旬蟋、GCD線程間的通信
在 iOS 開發(fā)過程中,我們在主線程進行UI刷新革娄,把圖片下載倾贰、文件上傳、網(wǎng)絡(luò)請求等一些耗時的操作放在其他的線程稠腊,當(dāng)這些耗時的操作完成后需要將數(shù)據(jù)同步給UI躁染,就需要回到主線程刷新UI鸣哀,那么就要用到線程之間的通訊架忌。GCD提供了非常簡便的方法進行線程間的通訊。
- (void)communication {
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
// 模擬耗時操作
sleep(2);
// 回到主線程
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
});
}
打印結(jié)果
2020-03-13 00:06:07.632014+0800 ThreadDemo[53480:7074047] 1---<NSThread: 0x60000063e480>{number = 5, name = (null)}
2020-03-13 00:06:09.633041+0800 ThreadDemo[53480:7073841] 2---<NSThread: 0x60000061a180>{number = 1, name = main}
從上面代碼運行的結(jié)果可以看出我衬,1是在子線程中執(zhí)行的叹放,隔2秒后打印2,2是在主線程中執(zhí)行的挠羔。
5井仰、GCD中的函數(shù)方法
GCD中提供了諸多的函數(shù)方法供開發(fā)者調(diào)用,我們一起來看下這些方法的使用破加。
5.1俱恶、柵欄方法
有的時候我們需要異步執(zhí)行兩組操作,等待第一組執(zhí)行完成后才回去執(zhí)行第二組操作范舀,這個時候柵欄方法就起作用了合是。
柵欄方法(dispatch_barrier_async
或dispatch_barrier_sync
)會等前邊追加到隊列中的任務(wù)執(zhí)行完畢后,再將制定的任務(wù)追加到隊列中锭环,然后等到dispatch_barrier_async
或dispatch_barrier_sync
方法追加的任務(wù)執(zhí)行完畢后才會去執(zhí)行后邊追加到隊列中的任務(wù)聪全,簡單來說dispatch_barrier_async
或dispatch_barrier_sync
將異步任務(wù)分成了兩個組,執(zhí)行完第一組后辅辩,再執(zhí)行自己难礼,然后執(zhí)行隊列中剩余的任務(wù)娃圆。唯一不同的是dispatch_barrier_async
不會阻塞線程。
看如下代碼:
- (void)barrierTask {
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"start");
dispatch_async(queue, ^{
NSLog(@"currentThread-1:%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"currentThread-2:%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
});
NSLog(@"pause");
dispatch_async(queue, ^{
NSLog(@"currentThread-3:%@", [NSThread currentThread]);
});
NSLog(@"end");
}
打印結(jié)果
2020-03-13 00:31:53.247514+0800 ThreadDemo[54101:7100220] start
2020-03-13 00:31:53.247730+0800 ThreadDemo[54101:7100220] pause
2020-03-13 00:31:53.247809+0800 ThreadDemo[54101:7100396] currentThread-1:<NSThread: 0x600003b8db00>{number = 5, name = (null)}
2020-03-13 00:31:53.247883+0800 ThreadDemo[54101:7100220] end
2020-03-13 00:31:53.247991+0800 ThreadDemo[54101:7100396] currentThread-2:<NSThread: 0x600003b8db00>{number = 5, name = (null)}
2020-03-13 00:31:55.250622+0800 ThreadDemo[54101:7100396] currentThread-3:<NSThread: 0x600003b8db00>{number = 5, name = (null)}
從上面的代碼運行結(jié)果可以看出蛾茉,start讼呢、pause、end都是在2執(zhí)行答應(yīng)的臀稚,說明dispatch_barrier_async并沒有阻塞線程吝岭,3是在2打印兩秒后打印的。
如果把dispatch_barrier_async換成dispatch_barrier_sync打印結(jié)果會是怎么樣呢吧寺?
- (void)barrierTask {
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"start");
dispatch_async(queue, ^{
NSLog(@"currentThread-1:%@", [NSThread currentThread]);
});
dispatch_barrier_sync(queue, ^{
NSLog(@"currentThread-2:%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
});
NSLog(@"pause");
dispatch_async(queue, ^{
NSLog(@"currentThread-3:%@", [NSThread currentThread]);
});
NSLog(@"end");
}
打印結(jié)果
2020-03-13 00:35:01.460109+0800 ThreadDemo[54171:7103212] start
2020-03-13 00:35:01.460408+0800 ThreadDemo[54171:7103379] currentThread-1:<NSThread: 0x600002508540>{number = 6, name = (null)}
2020-03-13 00:35:01.460588+0800 ThreadDemo[54171:7103212] currentThread-2:<NSThread: 0x600002570880>{number = 1, name = main}
2020-03-13 00:35:03.461678+0800 ThreadDemo[54171:7103212] pause
2020-03-13 00:35:03.462012+0800 ThreadDemo[54171:7103212] end
2020-03-13 00:35:03.462145+0800 ThreadDemo[54171:7103379] currentThread-3:<NSThread: 0x600002508540>{number = 6, name = (null)}
從上面代碼運行的結(jié)果可以看出窜管,pause和end是在2之后打印的,說明dispatch_barrier_sync阻塞了線程稚机,需要等待dispatch_barrier_sync執(zhí)行完成后才會往后執(zhí)行幕帆。
5.2、延時執(zhí)行方法-dispatch_after
延時執(zhí)行任務(wù)相信對于iOS開發(fā)者來說并不陌生赖条,我們經(jīng)常遇到需要在延后指定之間后執(zhí)行某個操作的需求失乾,那么這種需求用GCD來實現(xiàn)是很方便的。GCD的延時執(zhí)行的函數(shù)是dispatch_after
纬乍。需要注意的是:dispatch_after
方法并不是在指定時間之后才開始執(zhí)行處理碱茁,而是在指定時間之后將任務(wù)追加到主隊列中。嚴(yán)格來說仿贬,這個時間并不是絕對準(zhǔn)確的纽竣,但想要大致延遲執(zhí)行任務(wù),dispatch_after
方法是很有效的茧泪。
-(void)afterTask{
NSLog(@"begin");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"after---%@",[NSThread currentThread]);
});
}
打印結(jié)果
2020-03-13 00:43:47.787769+0800 ThreadDemo[54378:7111012] begin
2020-03-13 00:43:50.788086+0800 ThreadDemo[54378:7111012] after---<NSThread: 0x60000042ddc0>{number = 1, name = main}
從上面代碼的運行結(jié)果可以看出afer是在begin打印后3秒才打印的蜓氨。
5.3、一次性代碼-dispatch_once
GCD提供了只執(zhí)行一次的方法dispatch_once
队伟,這個方法在我們創(chuàng)建單例的時候回經(jīng)常用到穴吹。dispatch_once
方法可以保證一段代碼在程序運行過程中只被調(diào)用一次,而且在多線程環(huán)境下可以保證線程安全嗜侮。
+ (instancetype)shareInstance{
static Test *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [Test alloc]init];
});
return instance;
}
5.5港令、調(diào)度組
調(diào)度組簡單來說就是把異步執(zhí)行的任務(wù)進行分組,等待所有的分組任務(wù)都執(zhí)行完畢后再回到指定的線程執(zhí)行任務(wù)锈颗。調(diào)用組使用dispatch_group_create
來創(chuàng)建一個分組顷霹,dispatch_group_async
方法先把任務(wù)添加到隊列中,然后將隊列方到調(diào)度組中宜猜,或者也可以使用dispatch_group_enter
和dispatch_group_leave
捉對實現(xiàn)將隊列添加到調(diào)度組泼返。調(diào)用dispatch_group_notify
方法回到指定線程執(zhí)行任務(wù),或者調(diào)用dispatch_group_wait
阻塞當(dāng)前線程姨拥。
- (void)groupNotifyTest{
NSLog(@"current thread:%@", [NSThread currentThread]);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"thread-1:%@", [NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"thread-2:%@", [NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"thread-3:%@", [NSThread currentThread]);
NSLog(@"group-end");
});
//會阻塞線程绅喉,
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"dispatch_group_wait后繼續(xù)執(zhí)行任務(wù)");
}
打印結(jié)果:
2020-03-14 23:58:09.871023+0800 ThreadDemo[77370:8259501] current thread:<NSThread: 0x6000019fe480>{number = 1, name = main}
2020-03-14 23:58:11.874345+0800 ThreadDemo[77370:8260290] thread-2:<NSThread: 0x600001996e80>{number = 7, name = (null)}
2020-03-14 23:58:11.874343+0800 ThreadDemo[77370:8259684] thread-1:<NSThread: 0x6000019a55c0>{number = 5, name = (null)}
2020-03-14 23:58:11.874672+0800 ThreadDemo[77370:8259501] dispatch_group_wait后繼續(xù)執(zhí)行任務(wù)
2020-03-14 23:58:13.877077+0800 ThreadDemo[77370:8259501] thread-3:<NSThread: 0x6000019fe480>{number = 1, name = main}
2020-03-14 23:58:13.877365+0800 ThreadDemo[77370:8259501] group-end
在這里需要說明的一點是
dispatch_group_wait
渠鸽,該方法需要傳入兩個參數(shù),第一個參數(shù)是group即調(diào)度組柴罐,第二個參數(shù)是timerout即指定等待的時間徽缚。一旦調(diào)用dispatch_group_wait
函數(shù),該函數(shù)就處理調(diào)用的狀態(tài)而不返回值,只有當(dāng)函數(shù)的currentThread停止革屠,或到達wait函數(shù)指定的等待的時間凿试,或Dispatch Group
中的操作全部執(zhí)行完畢之前,執(zhí)行該函數(shù)的線程停止似芝。當(dāng)指定timeout為DISPATCH_TIME_FOREVER
時就意味著永久等待那婉;當(dāng)指定timeout為DISPATCH_TIME_NOW
時就意味不用任何等待即可判定屬于Dispatch Group
的處理是否全部執(zhí)行結(jié)束。如果dispatch_group_wait
函數(shù)返回值不為0党瓮,就意味著雖然經(jīng)過了指定的時間详炬,但Dispatch Group
中的操作并未全部執(zhí)行完畢。如果dispatch_group_wait
函數(shù)返回值為0寞奸,就意味著Dispatch Group
中的操作全部執(zhí)行完畢呛谜。
下面看dispatch_group_enter
和dispatch_group_leave
捉對實現(xiàn)將隊列添加到調(diào)度組的情況。dispatch_group_enter
標(biāo)志著在group的任務(wù)數(shù)+1枪萄,dispatch_group_leave
標(biāo)志著在group中的任務(wù)數(shù)-1隐岛,表示已經(jīng)完成了一個任務(wù)。
- (void)groupEnterTest {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"thread_1:%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"thread_2:%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"thread_3:%@", [NSThread currentThread]);
NSLog(@"group_end");
});
}
打印結(jié)果:
2020-03-15 00:07:17.293333+0800 ThreadDemo[77525:8269621] thread_2:<NSThread: 0x600002434ac0>{number = 7, name = (null)}
2020-03-15 00:07:17.293320+0800 ThreadDemo[77525:8269427] thread_1:<NSThread: 0x60000241b9c0>{number = 3, name = (null)}
2020-03-15 00:07:19.294186+0800 ThreadDemo[77525:8269235] thread_3:<NSThread: 0x600002450b80>{number = 1, name = main}
2020-03-15 00:07:19.294485+0800 ThreadDemo[77525:8269235] group_end
需要注意的是
dispatch_group_enter
和dispatch_group_leave
捉對出現(xiàn)的瓷翻。
5.6聚凹、信號量
GCD中的信號量是指的Dispatch Semaphore
,是持有計數(shù)的信號逻悠。當(dāng)信號量小于0時就會一直等待即阻塞所在線程元践,否則就可以正常執(zhí)行韭脊。信號量可以保持線程的同步童谒,將異步執(zhí)行任務(wù)轉(zhuǎn)換成同步任務(wù)執(zhí)行, 同時保持線程的安全沪羔。
Dispatch Semaphore提供了三個方法:
-
dispatch_semaphore_create
:創(chuàng)建一個 Semaphore 并初始化信號的總量 -
dispatch_semaphore_signal
:發(fā)送一個信號饥伊,讓信號總量加 1 -
dispatch_semaphore_wait
:可以使總信號量減 1,信號總量小于 0 時就會一直等待(阻塞所在線程)蔫饰,否則就可以正常執(zhí)行琅豆。
- (void)semaphoreTest {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int a = 0;
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"里面的a的值:%d-----%@", a, [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
a++;
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
NSLog(@"外面的a的值:%d", a);
}
打印結(jié)果:
2020-03-15 00:44:53.005899+0800 ThreadDemo[78303:8318841] 里面的a的值:0-----<NSThread: 0x600003c222c0>{number = 3, name = (null)}
2020-03-15 00:44:53.006161+0800 ThreadDemo[78303:8318841] 里面的a的值:1-----<NSThread: 0x600003c222c0>{number = 3, name = (null)}
2020-03-15 00:44:53.006354+0800 ThreadDemo[78303:8318841] 里面的a的值:2-----<NSThread: 0x600003c222c0>{number = 3, name = (null)}
2020-03-15 00:44:53.006551+0800 ThreadDemo[78303:8318841] 里面的a的值:3-----<NSThread: 0x600003c222c0>{number = 3, name = (null)}
2020-03-15 00:44:53.006727+0800 ThreadDemo[78303:8318841] 里面的a的值:4-----<NSThread: 0x600003c222c0>{number = 3, name = (null)}
2020-03-15 00:44:53.006862+0800 ThreadDemo[78303:8318672] 外面的a的值:5
5.7、調(diào)度源-Dispatch_source
調(diào)度源是協(xié)調(diào)特殊低級別系統(tǒng)事件處理的基本數(shù)據(jù)類型篓吁。GCD支持諸如定時器調(diào)度源茫因、信號調(diào)度源、描述符調(diào)度源杖剪、進程調(diào)度源冻押、端口調(diào)度源驰贷、自定義調(diào)度源等。調(diào)度源有這一系列的成熟API洛巢,在這里就不多做描述括袒,詳細可以查閱官方的文檔Dispatch Sources。