談到編程,就離不開多線程。多線程提升了系統(tǒng)資源的利用率,使得程序在相同時間單位里可以做更多的事情背犯,是我們每個程序員都必須掌握的重要知識坏瘩。
-
什么是線程
線程(英語:thread)是操作系統(tǒng)能夠進(jìn)行運算調(diào)度的最小單位盅抚。它被包含在進(jìn)程之中,是進(jìn)程中的實際運作單位倔矾。一條線程指的是進(jìn)程中一個單一順序的控制流妄均,一個進(jìn)程中可以并發(fā)多個線程柱锹,每條線程并行執(zhí)行不同的任務(wù)。
-
什么是進(jìn)程
進(jìn)程(英語:process)丰包,是計算機中已運行程序的實體禁熏。進(jìn)程為曾經(jīng)是分時系統(tǒng)的基本運作單位。在面向進(jìn)程設(shè)計的系統(tǒng)(如早期的UNIX邑彪,Linux 2.4及更早的版本)中瞧毙,進(jìn)程是程序的基本執(zhí)行實體;在面向線程設(shè)計的系統(tǒng)(如當(dāng)代多數(shù)操作系統(tǒng)寄症、Linux 2.6及更新的版本)中宙彪,進(jìn)程本身不是基本運行單位,而是線程的容器有巧。程序本身只是指令释漆、數(shù)據(jù)及其組織形式的描述,進(jìn)程才是程序(那些指令和數(shù)據(jù))的真正運行實例篮迎。若干進(jìn)程有可能與同一個程序相關(guān)系男图,且每個進(jìn)程皆可以同步(循序)或異步(平行)的方式獨立運行。現(xiàn)代計算機系統(tǒng)可在同一段時間內(nèi)以進(jìn)程的形式將多個程序加載到內(nèi)存中甜橱,并借由時間共享(或稱時分復(fù)用)逊笆,以在一個處理器上表現(xiàn)出同時(平行性)運行的感覺。同樣的渗鬼,使用多線程技術(shù)(多線程即每一個線程都代表一個進(jìn)程內(nèi)的一個獨立執(zhí)行上下文)的操作系統(tǒng)或計算機架構(gòu)览露,同樣程序的平行線程,可在多CPU主機或網(wǎng)絡(luò)上真正同時運行(在不同的CPU上)譬胎。
-
什么是程序
程序(英語:procedure)差牛,指特定的一系列動作、行動或操作堰乔,而這些活動偏化、動作或操作必須以相同方式運行,借此在相同環(huán)境下恒常得出相同的結(jié)果(例如緊急應(yīng)變程序)镐侯。粗略而言侦讨,程序可以指一序列的活動、作業(yè)苟翻、步驟韵卤、決斷、計算和工序崇猫,當(dāng)它們保證依照嚴(yán)格規(guī)定的順序發(fā)生時即產(chǎn)生所述的后果沈条、產(chǎn)品或局面。一個程序通常引致一個改變∽缏現(xiàn)在小孩也可以寫程序蜡歹。
我們經(jīng)常容易混淆或者不知道如何對這三個概念明確的分界屋厘,我這里有一個簡單的區(qū)分方法:程序真正運行在計算機中的實體,就變成了進(jìn)程月而,(正在運行的程序叫進(jìn)程)而進(jìn)程可以包含一個或多個線程汗洒。
今天,我們談?wù)勗贠C中父款,多線程的幾種方案溢谤。其中pthread是純C語言的一套線程管理方案,由于在iOS開發(fā)中基本上不會使用憨攒,我們這里不做討論溯香。我們重點討論以下幾種方案:
-
NSThread
NSThread
有兩種創(chuàng)建方法,一種是類方法浓恶,一種是實例方法玫坛,類方法創(chuàng)建的時候需要確定好執(zhí)行的任務(wù),沒有任何返回包晰,它會自己創(chuàng)建一個新線程去執(zhí)行指定任務(wù)湿镀,而用實例方法則需要我們手動開啟這個線程。創(chuàng)建的時候我們既可以使用SEL
也可以使用Block
伐憾,為了簡化代碼勉痴,我們都使用Block
來創(chuàng)建。
//類方法創(chuàng)建
[NSThread detachNewThreadWithBlock:^{
for (int i = 0; i < 5; i++) {
NSLog(@"%d",i);
[NSThread sleepForTimeInterval:1];
}
}];
//實例方法創(chuàng)建
NSThread *thread = [[NSThread alloc] initWithBlock:^{
for (int i = 97; i < 102; i++) {
NSLog(@"%c",i);
[NSThread sleepForTimeInterval:1];
}
}];
[thread start];
運行結(jié)果:
我們可以看到這兩個線程是完全異步的树肃。運行幾次蒸矛,結(jié)果也不相同。
值得注意的是胸嘴,一個NSThread
線程的啟動雏掠,有三種方式,我們前面已經(jīng)看到了兩種劣像,一種是類方法直接創(chuàng)建并啟動乡话,另一種是[thread start];
方法,還有一種是[thread main];
耳奕,但是蘋果并不建議我們直接使用main
方法绑青,它可以在子類化的時候重寫并實現(xiàn)你的線程主體,而不用調(diào)用super屋群,任何時候闸婴,啟動線程都應(yīng)該用start
方法。
前面我們簡單得用NSThread
的方式創(chuàng)建了兩個線程芍躏,并且讓它們各自執(zhí)行了自己的任務(wù)邪乍。但是NSThread
可不止這么幾個方法,它還有很多比較有用的方法:
//設(shè)置名字
[thread setName:@"myThread"];
//設(shè)置優(yōu)先級,由0到1.0的浮點數(shù)指定溺欧,其中1.0是最高優(yōu)先級。
[thread setThreadPriority:1];
//退出當(dāng)前線程
[NSThread exit];
//睡眠 單位是秒
[NSThread sleepForTimeInterval:1];
//獲取當(dāng)前線程
[NSThread currentThread];
//獲取主線程
[NSThread mainThread];
//判斷是否在主線程
[NSThread isMainThread];
接下來柏肪,我們用經(jīng)典的賣票問題來模擬并解決NSThread
中的線程同步問題姐刁。有兩個售票員,同時開始賣票烦味,一共有20張票聂使,模擬該場景:
@property (nonatomic, assign) NSInteger tickets;
//初始有20張票
self.tickets = 20;
//創(chuàng)建兩個線程來充當(dāng)兩個售票員
[NSThread detachNewThreadWithBlock:^{
while (self.tickets > 0) {
[NSThread sleepForTimeInterval:1];
self.tickets --;
NSLog(@"還有%ld張票",(long)self.tickets);
}
}];
[NSThread detachNewThreadWithBlock:^{
while (self.tickets > 0) {
[NSThread sleepForTimeInterval:1];
self.tickets --;
NSLog(@"還有%ld張票",(long)self.tickets);
}
}];
運行結(jié)果:
通過打印結(jié)果,可以看出谬俄,一共賣出去了26張票柏靶,超出了我們的票的總數(shù),這是同一張票被多次出售的結(jié)果溃论。為了避免這種情況屎蜓,通常的做法就是上鎖,當(dāng)某一條線程對數(shù)據(jù)進(jìn)行操作時钥勋,先給數(shù)據(jù)上鎖炬转,別的線程阻塞,等到這條線程操作結(jié)束算灸,在開鎖扼劈,別的線程再進(jìn)去,上鎖菲驴,操作荐吵,開鎖……這樣就保證了數(shù)據(jù)的安全性。
我們可以使用@synchronized (object) {}
來進(jìn)行上鎖赊瞬,括號里的參數(shù)可以填任意對象先煎,但是要注意的是,必須填寫線程共有的變量才能實現(xiàn)上鎖巧涧,局部變量是無效的榨婆,原因是,如果用局部變量褒侧,就會創(chuàng)建多個鎖良风,這些鎖之間并無關(guān)聯(lián),所以與不上鎖沒有區(qū)別:
//創(chuàng)建兩個線程來充當(dāng)兩個售票員
[NSThread detachNewThreadWithBlock:^{
//對賣票過程加鎖
@synchronized (self) {
while (self.tickets > 0) {
[NSThread sleepForTimeInterval:1];
self.tickets --;
NSLog(@"還有%ld張票",(long)self.tickets);
}
}
}];
[NSThread detachNewThreadWithBlock:^{
//對賣票過程加鎖
@synchronized (self) {
while (self.tickets > 0) {
[NSThread sleepForTimeInterval:1];
self.tickets --;
NSLog(@"還有%ld張票",(long)self.tickets);
}
}
}];
運行結(jié)果:
因為在鎖內(nèi)進(jìn)行數(shù)據(jù)操作時闷供,其它線程都會阻塞在外面烟央,這個時候,其實線程不是并發(fā)執(zhí)行的歪脏,所以我們不難想到疑俭,鎖內(nèi)執(zhí)行的任務(wù)越少,那么這段代碼執(zhí)行的效率就越高婿失。在此基礎(chǔ)上钞艇,我們可以對前面的加鎖進(jìn)行一個小修改:
//創(chuàng)建兩個線程來充當(dāng)兩個售票員
[NSThread detachNewThreadWithBlock:^{
//對賣票過程加鎖
while (true) {
[NSThread sleepForTimeInterval:1];
@synchronized (self) {
if (self.tickets < 1) {
break;
}
self.tickets --;
NSLog(@"還有%ld張票",(long)self.tickets);
}
}
}];
[NSThread detachNewThreadWithBlock:^{
//對賣票過程加鎖
while (true) {
[NSThread sleepForTimeInterval:1];
@synchronized (self) {
if (self.tickets < 1) {
break;
}
self.tickets --;
NSLog(@"還有%ld張票",(long)self.tickets);
}
}
}];
運行結(jié)果:
注意看一下時間啄寡,修改以后,我們的賣票效率提升了一倍哩照,之前那種方式要20秒才能賣完挺物,現(xiàn)在只需要10秒。
當(dāng)然也可以用NSLock
來進(jìn)行上鎖飘弧,使用NSLock
需要創(chuàng)建一個NSLock
實例识藤,然后調(diào)用lock
和unlock
方法來進(jìn)行加鎖和解鎖的操作:
@property (nonatomic, strong) NSLock *lock;
//初始化鎖
self.lock = [[NSLock alloc] init];
//創(chuàng)建兩個線程來充當(dāng)兩個售票員
[NSThread detachNewThreadWithBlock:^{
//對賣票過程加鎖
while (true) {
[NSThread sleepForTimeInterval:1];
[self.lock lock];
if (self.tickets < 1) {
break;
}
self.tickets --;
NSLog(@"還有%ld張票",(long)self.tickets);
[self.lock unlock];
}
}];
[NSThread detachNewThreadWithBlock:^{
//對賣票過程加鎖
while (true) {
[NSThread sleepForTimeInterval:1];
[self.lock lock];
if (self.tickets < 1) {
break;
}
self.tickets --;
NSLog(@"還有%ld張票",(long)self.tickets);
[self.lock unlock];
}
}];
運行結(jié)果跟上面是一樣的。
不知道你還記得不記得atomic
次伶,這個就修飾了屬性的原子性痴昧,如果直接把屬性修飾改為atomic
,會不會就不需要我們加鎖了呢冠王?我試過赶撰,不行!這是因為atomic
只會對該屬性的Getter
和Setter
方法上鎖柱彻,而我們很顯然是在別的方法里面對數(shù)據(jù)進(jìn)行操作扣囊,所以并沒什么卵用。同時也因為atomic
太耗性能绒疗,所以在實際開發(fā)中侵歇,我們一般都不使用它來修飾變量。
-
GCD
Grand Central Dispatch (GCD)是什么吓蘑?
GCD中文翻譯過來是宏偉的中樞調(diào)度惕虑,是一種基于C語言的并發(fā)編程技術(shù)。它是蘋果為多核的并行運算提出的解決方案磨镶,會自動調(diào)度系統(tǒng)資源溃蔫,所以它的效率很高。
GCD并不直接操作線程琳猫,而是操作隊列和任務(wù)伟叛。我們只需要把任務(wù)添加到隊列里,然后指定任務(wù)執(zhí)行的方式脐嫂,GCD就會自動調(diào)度線程執(zhí)行任務(wù)统刮。
GCD的任務(wù)都是以Block
形式存在的。
隊列有兩種:串行隊列/并發(fā)隊列账千。
- 串行隊列只能等一個任務(wù)執(zhí)行完畢才可以繼續(xù)調(diào)度下一個任務(wù)
/* 創(chuàng)建一個串行隊列
* 參數(shù):1.名字2.類型侥蒙,DISPATCH_QUEUE_SERIAL(串行隊列) DISPATCH_QUEUE_CONCURRENT(并發(fā)隊列)
*/
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
- 并發(fā)隊列可以同時調(diào)度多個任務(wù)
/* 創(chuàng)建一個并發(fā)隊列
* 參數(shù):1.名字2.類型,DISPATCH_QUEUE_SERIAL(串行隊列) DISPATCH_QUEUE_CONCURRENT(并發(fā)隊列)
*/
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
執(zhí)行任務(wù)也有兩種方式:同步執(zhí)行/異步執(zhí)行匀奏。
- 同步執(zhí)行會等待當(dāng)前任務(wù)完成才會執(zhí)行下一個任務(wù)鞭衩,不會開啟新線程。
/* 同步執(zhí)行任務(wù)
* 參數(shù):1.隊列2.block(任務(wù))
*/
dispatch_sync(dispatch_queue_t _Nonnull queue, ^(void)block);
- 異步執(zhí)行不會等待當(dāng)前任務(wù)完成就會執(zhí)行下一個任務(wù),可以開啟新線程(如果是主隊列论衍,則不會開啟新線程瑞佩,因為主隊列的任務(wù)都會在主線程執(zhí)行)。
/* 異步執(zhí)行任務(wù)
* 參數(shù):1.隊列2.block(任務(wù))
*/
dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block);
隊列和任務(wù)都有兩種坯台,排列組合以后就有四種情況炬丸,在不同的情況下,執(zhí)行的結(jié)果可能會有差異捂人,如果不清楚原理比較容易混淆。這里有一個簡單的方法去分析執(zhí)行情況:隊列的類型決定了能不能同時執(zhí)行多個任務(wù)(串行隊列一次只能執(zhí)行一個任務(wù)矢沿,并發(fā)隊列一次可以執(zhí)行多個任務(wù))滥搭,執(zhí)行的方式?jīng)Q定了會不會開啟新線程(同步執(zhí)行不會開啟新線程,異步執(zhí)行可以開啟新線程)捣鲸。
了解以上的基礎(chǔ)瑟匆,我們就可以利用GCD進(jìn)行編程了,我們把創(chuàng)建好的隊列兩個隊列分別放到兩種執(zhí)行方式中栽惶,把這四種情況都演示一遍愁溜,任務(wù)都是在block
中打印0-9:
1.同步執(zhí)行串行隊列任務(wù)
//創(chuàng)建一個串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
//同步執(zhí)行串行隊列任務(wù)
for (int i = 0; i < 10; i ++) {
dispatch_sync(serialQueue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
}
運行結(jié)果:
我們可以看到,同步執(zhí)行的方式并沒有開啟新線程外厂,打印結(jié)果也是順序的冕象。
2.同步執(zhí)行并發(fā)隊列任務(wù)
//創(chuàng)建一個并發(fā)隊列
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
//同步執(zhí)行并發(fā)隊列任務(wù)
for (int i = 0; i < 10; i ++) {
dispatch_sync(concurrentQueue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
}
運行結(jié)果:
可以看到同步執(zhí)行的情況下,無論是串行隊列還是并發(fā)隊列汁蝶,結(jié)果并沒有區(qū)別渐扮,這是因為在同步執(zhí)行的情況下并不會開啟新的線程,所有任務(wù)都只能在一條線程上執(zhí)行掖棉,而同一條線程上的任務(wù)只能串行執(zhí)行墓律,所以即使并發(fā)隊列擁有同時調(diào)度多個任務(wù)的能力,但是在一條線程的情況下幔亥,也只能等前一個任務(wù)執(zhí)行完畢再調(diào)度新的任務(wù)去執(zhí)行耻讽。所以,在同步執(zhí)行任務(wù)的情況下帕棉,串行隊列和并發(fā)隊列的運行結(jié)果是一致的针肥。
3.異步執(zhí)行串行隊列任務(wù)
//創(chuàng)建一個串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
//異步執(zhí)行串行隊列任務(wù)
for (int i = 0; i < 10; i ++) {
dispatch_async(serialQueue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
}
運行結(jié)果:
可以看到,這種情況下香伴,任務(wù)是順序執(zhí)行的祖驱,但是它是在子線程執(zhí)行的。這是因為瞒窒,異步執(zhí)行可以開啟新線程捺僻,但是由于是串行隊列,所以任務(wù)只能一個一個順序執(zhí)行。
4.異步執(zhí)行并發(fā)隊列任務(wù)
//創(chuàng)建一個并發(fā)隊列
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
//異步執(zhí)行并發(fā)隊列任務(wù)
for (int i = 0; i < 10; i ++) {
dispatch_async(concurrentQueue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
}
運行結(jié)果:
可以看到匕坯,這種情況下束昵,執(zhí)行任務(wù)的順序不固定,并且會開啟多條線程同時執(zhí)行葛峻,所以這種時候執(zhí)行任務(wù)的效率最高锹雏。
在實際的開發(fā)中,我們更多運用到的還是異步執(zhí)行术奖,畢竟我們運用多線程技術(shù)是為了在另一條線程上執(zhí)行任務(wù)礁遵,至于選擇串行隊列還是并發(fā)隊列就要根據(jù)實際情況來判斷了:
- 如果隊列里的任務(wù)必須按照順序執(zhí)行,那就選擇串行隊列采记。
- 如果隊列里的任務(wù)沒有執(zhí)行順序的需求佣耐,那最好選擇并發(fā)隊列,因為并發(fā)隊列的執(zhí)行效率更高唧龄。
系統(tǒng)也為我們提供了兩種隊列兼砖,分別是:全局隊列dispatch_get_global_queue(long identifier, unsigned long flags)
、主隊列dispatch_get_main_queue()
既棺。
全局隊列本質(zhì)上是一個并發(fā)隊列讽挟,可以通過前面的測試來證明,獲取時需要傳遞參數(shù)丸冕,第一個參數(shù)是服務(wù)質(zhì)量的選擇(以前叫優(yōu)先級)耽梅,第二個是保留參數(shù),暫時只需要傳0就可以了:
//全局隊列1.優(yōu)先級或服務(wù)質(zhì)量胖烛,2.保留參數(shù)褐墅,目前傳0
/*
* 優(yōu)先級和服務(wù)質(zhì)量的對應(yīng)關(guān)系:
* - DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED
* - DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT
* - DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY
* - DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
*/
//默認(rèn)優(yōu)先級的全局隊列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
這里值得注意的是,一般情況下最好不要隨意選擇優(yōu)先級洪己,默認(rèn)就夠用了妥凳。優(yōu)先級本質(zhì)上是一個概率的問題,優(yōu)先級越高答捕,CPU調(diào)度的概率越高逝钥,并不具備確定性,如果出現(xiàn)問題拱镐,很難查找原因艘款。當(dāng)然,如果你很了解這些沃琅,并且就是為了性能和資源的考慮而做了優(yōu)先級的選擇哗咆,那么你可以無視這些。
主隊列不需要參數(shù)可以直接獲取,不過主隊列并不會開啟新線程,主隊列上的所有任務(wù)都只會在主線程上執(zhí)行,所以我們在平時的編程中稽荧,往往是在子線程中處理耗時操作年碘,然后在主線程更新UI澈歉。在實際開發(fā)中,我們經(jīng)常會遇到一種場景屿衅,就是在界面上顯示一張網(wǎng)絡(luò)圖片埃难,要顯示圖片肯定得先下載,而下載是一個耗時操作涤久,如果在主線程下載涡尘,那就會使界面卡住不能進(jìn)行其它操作,所以我們一般都會在子線程下載响迂,下載好以后再去主線程更新界面:
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(globalQueue, ^{
NSLog(@"在子線程執(zhí)行耗時操作考抄!");
dispatch_async(mainQueue, ^{
NSLog(@"在主線程更新UI");
});
});
以上的使用可以滿足一些簡單業(yè)務(wù)的需求,但是實際開發(fā)中有很多復(fù)雜業(yè)務(wù)栓拜,比如說在用戶登錄的時候需要同步多種信息座泳,而這些信息從不同的接口獲取惠昔,只有所有信息全部同步結(jié)束才可以正常操作幕与,同步各種信息應(yīng)該各自在子線程進(jìn)行,我們可以異步執(zhí)行并發(fā)隊列中的任務(wù)來做這些耗時操作镇防,但是我們怎么知道所有任務(wù)都執(zhí)行完了呢啦鸣?
GCD Group
GCD為我們提供了另一個東西,叫做Group(調(diào)度組)来氧。調(diào)度組是用來協(xié)調(diào)一個或多個任務(wù)提交到隊列異步觸發(fā)的诫给。 應(yīng)用程序可以使用調(diào)度組等待所有調(diào)度組中的所有任務(wù)的完成。
所有異步隊列執(zhí)行完畢后得到一個通知啦扬。
調(diào)度組的使用并不復(fù)雜中狂,它有兩種用法:
//創(chuàng)建一個調(diào)度組
dispatch_group_t group = dispatch_group_create();
//獲取全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//為隊列添加任務(wù),并且和給定的調(diào)度組關(guān)聯(lián)
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"同步信息1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"同步信息2");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:.5];
NSLog(@"同步信息3");
});
//所有任務(wù)執(zhí)行完畢通知
dispatch_group_notify(group, queue, ^{
NSLog(@"全部都完了");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"更新UI");
});
});
或者
//創(chuàng)建一個調(diào)度組
dispatch_group_t group = dispatch_group_create();
//獲取全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//手動添加一個任務(wù)到該調(diào)度組
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"同步信息1");
//該任務(wù)執(zhí)行完畢從調(diào)度組移除
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"同步信息2");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:.5];
NSLog(@"同步信息3");
dispatch_group_leave(group);
});
//等待所有任務(wù)執(zhí)行完畢 參數(shù):1.對應(yīng)的調(diào)度組 2.超時時間
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//所有任務(wù)執(zhí)行完畢才會來這里
dispatch_async(queue, ^{
NSLog(@"全部都完了");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"更新UI");
});
});
運行結(jié)果:
兩種用法的運行結(jié)果是相同的扑毡,第二種更加靈活一些胃榕,但是代碼量也相應(yīng)多一些,使用調(diào)度組我們可以更好的對任務(wù)進(jìn)行控制瞄摊,并且在特定的場景滿足我們的需求勋又。靈活使用調(diào)度組,可以讓我們對線程同步控制更加得心應(yīng)手换帜。
GCD還有一個很重要的功能楔壤,就是一次執(zhí)行。用這個代碼塊包含的代碼只會執(zhí)行一次惯驼,在實際開發(fā)中經(jīng)常使用蹲嚣,單例模式一般都會用GCD來做递瑰,因為它效率高:
for (int i = 0; i < 10; i++) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"你猜我會執(zhí)行幾次?");
});
}
運行以后只會打印一次端铛。
-
NSOperation
NSOperation
是對GCD面向?qū)ο蟮姆庋b泣矛。它擁有GCD的高效,也擁有面向?qū)ο蟮木幊趟枷牒滩稀:虶CD類似您朽,它也是把任務(wù)放在隊列里去執(zhí)行,不過它比GCD少了一些概念换淆,但是它也有了一些GCD沒有的功能哗总,接下來我們就從最開始了解NSOperation
。
Operation翻譯過來是操作的意思倍试,其實跟GCD的任務(wù)是一樣的讯屈。因為它本身就是GCD的封裝,所以在理解上也差不多县习。我們順著GCD的用法來使用NSOperation
涮母。
NSOperation
本身是個抽象類,我們要使用它就得使用它的子類躁愿。系統(tǒng)給我們提供了兩個叛本,分別是:NSInvocationOperation
和NSBlockOperation
。一種是通過selector的形式添加操作彤钟,一種是以block的形式添加操作来候,我個人更喜歡NSBlockOperation
,用起來更方便些逸雹。
分別用這兩種方式創(chuàng)建兩個操作(其實就是GCD的任務(wù)):
//NSBlockOperation
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation");
}];
//NSInvocationOperation
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationMethod) object:nil];
- (void)invocationMethod{
NSLog(@"invocationOperation%@",[NSThread currentThread]);
}
根據(jù)GCD的操作流程营搅,這時候就需要創(chuàng)建隊列了。NSOperation
的隊列也有一個類NSOperationQueue
梆砸,它的創(chuàng)建也很簡單:
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
然后把前面創(chuàng)建的操作添加到隊列中:
[operationQueue addOperation:blockOperation];
[operationQueue addOperation:invocationOperation];
這個地方也不需要指定它的執(zhí)行方式转质,直接把操作添加到隊列中就會自動異步執(zhí)行:
NSOperationQueue
本身也有通過block添加操作的方法,不需要我們專門去創(chuàng)建帖世,這樣就進(jìn)一步簡化了代碼:
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperationWithBlock:^{
NSLog(@"1%@",[NSThread currentThread]);
}];
[operationQueue addOperationWithBlock:^{
NSLog(@"2%@",[NSThread currentThread]);
}];
直接運行:
NSOperationQueue
并沒有全局隊列休蟹,但是我們可以自己根據(jù)需求創(chuàng)建全局隊列。NSOperationQueue
也有獲取主隊列的類方法[NSOperationQueue mainQueue];
狮暑,用起來也很簡單方便鸡挠,跟GCD中的主隊列一樣。
一個子線程處理耗時操作搬男,然后刷新UI的代碼拣展,使用NSOperationQueue
的方式就變成了這樣:
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperationWithBlock:^{
NSLog(@"子線程處理耗時操作%@", [NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"主線程更新UI%@", [NSThread currentThread]);
}];
}];
運行結(jié)果:
怎么樣,是不是用起來很簡單缔逛。
在線程同步上备埃,NSOperation
沒有group
姓惑,但是有操作依賴,一樣可以實現(xiàn)同樣的效果按脚。它的依賴于毙,是操作的方法,所以如果要使用依賴辅搬,我們就得自己創(chuàng)建操作唯沮,然后操作之間設(shè)置好依賴關(guān)系,再把它們丟到隊列里堪遂,比如說前面GCD中的那個同步信息的例子:
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"同步信息1");
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"同步信息2");
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:.5];
NSLog(@"同步信息3");
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"更新UI");
}];
[op4 addDependency:op1];
[op4 addDependency:op2];
[op4 addDependency:op3];
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperations:@[op1,op2,op3,op4] waitUntilFinished:NO];
這里介蛉,操作4依賴了操作1、2溶褪、3币旧,所以它得等到其它3個操作完成才能開始執(zhí)行,運行結(jié)果:
這里要注意猿妈,操作之間一定不能形成循環(huán)依賴吹菱,循環(huán)依賴的任務(wù)沒辦法執(zhí)行,因為都得再對方執(zhí)行完畢之后才滿足自己的執(zhí)行條件彭则。
這里在給隊列添加操作的時候鳍刷,用了新的方法[queue addOperations:@[op1,op2,op3,op4] waitUntilFinished:NO];
,這個方法可以一次性添加多個操作贰剥,但是后面有一個參數(shù)倾剿,看名字我們就可以知道筷频,如果傳YES
蚌成,它會一直等所有任務(wù)都執(zhí)行完畢才會繼續(xù)執(zhí)行下面的任務(wù),有點類似于GCD
里面group的那個wait
凛捏,但是它會卡主當(dāng)前線程担忧,所以不能在主線程中使用,我們也可以在子線程里面執(zhí)行這一段代碼坯癣,稍加改造瓶盛,達(dá)到同樣的效果:
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperationWithBlock:^{
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"同步信息1");
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"同步信息2");
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:.5];
NSLog(@"同步信息3");
}];
[queue addOperations:@[op1,op2,op3] waitUntilFinished:YES];
NSLog(@"更新UI");
}];
運行結(jié)果是一樣的,這里就不貼圖了示罗。
不知道你有沒有注意到惩猫,NSOperationQueue
本身并沒有并發(fā)隊列和串行隊列的選項,它默認(rèn)是并發(fā)隊列蚜点,但是轧房,它有一個maxConcurrentOperationCount
屬性(代表了最大并發(fā)數(shù),也就是最多能夠開幾條線程執(zhí)行操作)绍绘,如果最大并發(fā)數(shù)量為1奶镶,它就變成了類似串行隊列的模樣迟赃。
NSOperationQueue
還可以使用suspended
屬性來控制隊列里操作的暫停和繼續(xù)。使用cancelAllOperations
方法來取消隊列里的所有操作厂镇。這些簡單的屬性和方法就不專門演示了纤壁。
-
總結(jié)
以上就是OC中的多線程,在實際開發(fā)中捺信,我們幾乎不會使用pthread
酌媒,很少會使用NSThread
,不過NSThread
的一些類方法會經(jīng)常使用迄靠,比如獲取當(dāng)前線程馍佑,睡眠當(dāng)前線程等。GCD
和NSOperation
使用起來都效率更高梨水,并且操作簡單拭荤,是我們更好的選擇。它倆之間的選擇一般沒有明確的分界線疫诽,可以根據(jù)實際需求來選擇舅世,不過一般中小型項目多使用GCD
,大型項目多使用NSOperation
奇徒,可能是因為GCD
更底層
雏亚,更輕
一些,而NSOperation
更規(guī)范
摩钙,同時也重
了些罢低。
與本文相關(guān)的代碼點擊前往。