談到編程,就離不開多線程。多線程提升了系統資源的利用率,使得程序在相同時間單位里可以做更多的事情,是我們每個程序員都必須掌握的重要知識。
-
什么是線程
線程(英語:thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發多個線程,每條線程并行執行不同的任務。
-
什么是進程
進程(英語:process),是計算機中已運行程序的實體。進程為曾經是分時系統的基本運作單位。在面向進程設計的系統(如早期的UNIX,Linux 2.4及更早的版本)中,進程是程序的基本執行實體;在面向線程設計的系統(如當代多數操作系統、Linux 2.6及更新的版本)中,進程本身不是基本運行單位,而是線程的容器。程序本身只是指令、數據及其組織形式的描述,進程才是程序(那些指令和數據)的真正運行實例。若干進程有可能與同一個程序相關系,且每個進程皆可以同步(循序)或異步(平行)的方式獨立運行。現代計算機系統可在同一段時間內以進程的形式將多個程序加載到內存中,并借由時間共享(或稱時分復用),以在一個處理器上表現出同時(平行性)運行的感覺。同樣的,使用多線程技術(多線程即每一個線程都代表一個進程內的一個獨立執行上下文)的操作系統或計算機架構,同樣程序的平行線程,可在多CPU主機或網絡上真正同時運行(在不同的CPU上)。
-
什么是程序
程序(英語:procedure),指特定的一系列動作、行動或操作,而這些活動、動作或操作必須以相同方式運行,借此在相同環境下恒常得出相同的結果(例如緊急應變程序)。粗略而言,程序可以指一序列的活動、作業、步驟、決斷、計算和工序,當它們保證依照嚴格規定的順序發生時即產生所述的后果、產品或局面。一個程序通常引致一個改變。現在小孩也可以寫程序。
我們經常容易混淆或者不知道如何對這三個概念明確的分界,我這里有一個簡單的區分方法:程序真正運行在計算機中的實體,就變成了進程,(正在運行的程序叫進程)而進程可以包含一個或多個線程。
今天,我們談談在OC中,多線程的幾種方案。其中pthread是純C語言的一套線程管理方案,由于在iOS開發中基本上不會使用,我們這里不做討論。我們重點討論以下幾種方案:
-
NSThread
NSThread
有兩種創建方法,一種是類方法,一種是實例方法,類方法創建的時候需要確定好執行的任務,沒有任何返回,它會自己創建一個新線程去執行指定任務,而用實例方法則需要我們手動開啟這個線程。創建的時候我們既可以使用SEL
也可以使用Block
,為了簡化代碼,我們都使用Block
來創建。
//類方法創建
[NSThread detachNewThreadWithBlock:^{
for (int i = 0; i < 5; i++) {
NSLog(@"%d",i);
[NSThread sleepForTimeInterval:1];
}
}];
//實例方法創建
NSThread *thread = [[NSThread alloc] initWithBlock:^{
for (int i = 97; i < 102; i++) {
NSLog(@"%c",i);
[NSThread sleepForTimeInterval:1];
}
}];
[thread start];
運行結果:
我們可以看到這兩個線程是完全異步的。運行幾次,結果也不相同。
值得注意的是,一個NSThread
線程的啟動,有三種方式,我們前面已經看到了兩種,一種是類方法直接創建并啟動,另一種是[thread start];
方法,還有一種是[thread main];
,但是蘋果并不建議我們直接使用main
方法,它可以在子類化的時候重寫并實現你的線程主體,而不用調用super,任何時候,啟動線程都應該用start
方法。
前面我們簡單得用NSThread
的方式創建了兩個線程,并且讓它們各自執行了自己的任務。但是NSThread
可不止這么幾個方法,它還有很多比較有用的方法:
//設置名字
[thread setName:@"myThread"];
//設置優先級,由0到1.0的浮點數指定,其中1.0是最高優先級。
[thread setThreadPriority:1];
//退出當前線程
[NSThread exit];
//睡眠 單位是秒
[NSThread sleepForTimeInterval:1];
//獲取當前線程
[NSThread currentThread];
//獲取主線程
[NSThread mainThread];
//判斷是否在主線程
[NSThread isMainThread];
接下來,我們用經典的賣票問題來模擬并解決NSThread
中的線程同步問題。有兩個售票員,同時開始賣票,一共有20張票,模擬該場景:
@property (nonatomic, assign) NSInteger tickets;
//初始有20張票
self.tickets = 20;
//創建兩個線程來充當兩個售票員
[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);
}
}];
運行結果:
通過打印結果,可以看出,一共賣出去了26張票,超出了我們的票的總數,這是同一張票被多次出售的結果。為了避免這種情況,通常的做法就是上鎖,當某一條線程對數據進行操作時,先給數據上鎖,別的線程阻塞,等到這條線程操作結束,在開鎖,別的線程再進去,上鎖,操作,開鎖……這樣就保證了數據的安全性。
我們可以使用@synchronized (object) {}
來進行上鎖,括號里的參數可以填任意對象,但是要注意的是,必須填寫線程共有的變量才能實現上鎖,局部變量是無效的,原因是,如果用局部變量,就會創建多個鎖,這些鎖之間并無關聯,所以與不上鎖沒有區別:
//創建兩個線程來充當兩個售票員
[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);
}
}
}];
運行結果:
因為在鎖內進行數據操作時,其它線程都會阻塞在外面,這個時候,其實線程不是并發執行的,所以我們不難想到,鎖內執行的任務越少,那么這段代碼執行的效率就越高。在此基礎上,我們可以對前面的加鎖進行一個小修改:
//創建兩個線程來充當兩個售票員
[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);
}
}
}];
運行結果:
注意看一下時間,修改以后,我們的賣票效率提升了一倍,之前那種方式要20秒才能賣完,現在只需要10秒。
當然也可以用NSLock
來進行上鎖,使用NSLock
需要創建一個NSLock
實例,然后調用lock
和unlock
方法來進行加鎖和解鎖的操作:
@property (nonatomic, strong) NSLock *lock;
//初始化鎖
self.lock = [[NSLock alloc] init];
//創建兩個線程來充當兩個售票員
[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];
}
}];
運行結果跟上面是一樣的。
不知道你還記得不記得atomic
,這個就修飾了屬性的原子性,如果直接把屬性修飾改為atomic
,會不會就不需要我們加鎖了呢?我試過,不行!這是因為atomic
只會對該屬性的Getter
和Setter
方法上鎖,而我們很顯然是在別的方法里面對數據進行操作,所以并沒什么卵用。同時也因為atomic
太耗性能,所以在實際開發中,我們一般都不使用它來修飾變量。
-
GCD
Grand Central Dispatch (GCD)是什么?
GCD中文翻譯過來是宏偉的中樞調度,是一種基于C語言的并發編程技術。它是蘋果為多核的并行運算提出的解決方案,會自動調度系統資源,所以它的效率很高。
GCD并不直接操作線程,而是操作隊列和任務。我們只需要把任務添加到隊列里,然后指定任務執行的方式,GCD就會自動調度線程執行任務。
GCD的任務都是以Block
形式存在的。
隊列有兩種:串行隊列/并發隊列。
- 串行隊列只能等一個任務執行完畢才可以繼續調度下一個任務
/* 創建一個串行隊列
* 參數:1.名字2.類型,DISPATCH_QUEUE_SERIAL(串行隊列) DISPATCH_QUEUE_CONCURRENT(并發隊列)
*/
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
- 并發隊列可以同時調度多個任務
/* 創建一個并發隊列
* 參數:1.名字2.類型,DISPATCH_QUEUE_SERIAL(串行隊列) DISPATCH_QUEUE_CONCURRENT(并發隊列)
*/
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
執行任務也有兩種方式:同步執行/異步執行。
- 同步執行會等待當前任務完成才會執行下一個任務,不會開啟新線程。
/* 同步執行任務
* 參數:1.隊列2.block(任務)
*/
dispatch_sync(dispatch_queue_t _Nonnull queue, ^(void)block);
- 異步執行不會等待當前任務完成就會執行下一個任務,可以開啟新線程(如果是主隊列,則不會開啟新線程,因為主隊列的任務都會在主線程執行)。
/* 異步執行任務
* 參數:1.隊列2.block(任務)
*/
dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block);
隊列和任務都有兩種,排列組合以后就有四種情況,在不同的情況下,執行的結果可能會有差異,如果不清楚原理比較容易混淆。這里有一個簡單的方法去分析執行情況:隊列的類型決定了能不能同時執行多個任務(串行隊列一次只能執行一個任務,并發隊列一次可以執行多個任務),執行的方式決定了會不會開啟新線程(同步執行不會開啟新線程,異步執行可以開啟新線程)。
了解以上的基礎,我們就可以利用GCD進行編程了,我們把創建好的隊列兩個隊列分別放到兩種執行方式中,把這四種情況都演示一遍,任務都是在block
中打印0-9:
1.同步執行串行隊列任務
//創建一個串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
//同步執行串行隊列任務
for (int i = 0; i < 10; i ++) {
dispatch_sync(serialQueue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
}
運行結果:
我們可以看到,同步執行的方式并沒有開啟新線程,打印結果也是順序的。
2.同步執行并發隊列任務
//創建一個并發隊列
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
//同步執行并發隊列任務
for (int i = 0; i < 10; i ++) {
dispatch_sync(concurrentQueue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
}
運行結果:
可以看到同步執行的情況下,無論是串行隊列還是并發隊列,結果并沒有區別,這是因為在同步執行的情況下并不會開啟新的線程,所有任務都只能在一條線程上執行,而同一條線程上的任務只能串行執行,所以即使并發隊列擁有同時調度多個任務的能力,但是在一條線程的情況下,也只能等前一個任務執行完畢再調度新的任務去執行。所以,在同步執行任務的情況下,串行隊列和并發隊列的運行結果是一致的。
3.異步執行串行隊列任務
//創建一個串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
//異步執行串行隊列任務
for (int i = 0; i < 10; i ++) {
dispatch_async(serialQueue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
}
運行結果:
可以看到,這種情況下,任務是順序執行的,但是它是在子線程執行的。這是因為,異步執行可以開啟新線程,但是由于是串行隊列,所以任務只能一個一個順序執行。
4.異步執行并發隊列任務
//創建一個并發隊列
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
//異步執行并發隊列任務
for (int i = 0; i < 10; i ++) {
dispatch_async(concurrentQueue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
}
運行結果:
可以看到,這種情況下,執行任務的順序不固定,并且會開啟多條線程同時執行,所以這種時候執行任務的效率最高。
在實際的開發中,我們更多運用到的還是異步執行,畢竟我們運用多線程技術是為了在另一條線程上執行任務,至于選擇串行隊列還是并發隊列就要根據實際情況來判斷了:
- 如果隊列里的任務必須按照順序執行,那就選擇串行隊列。
- 如果隊列里的任務沒有執行順序的需求,那最好選擇并發隊列,因為并發隊列的執行效率更高。
系統也為我們提供了兩種隊列,分別是:全局隊列dispatch_get_global_queue(long identifier, unsigned long flags)
、主隊列dispatch_get_main_queue()
。
全局隊列本質上是一個并發隊列,可以通過前面的測試來證明,獲取時需要傳遞參數,第一個參數是服務質量的選擇(以前叫優先級),第二個是保留參數,暫時只需要傳0就可以了:
//全局隊列1.優先級或服務質量,2.保留參數,目前傳0
/*
* 優先級和服務質量的對應關系:
* - 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
*/
//默認優先級的全局隊列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
這里值得注意的是,一般情況下最好不要隨意選擇優先級,默認就夠用了。優先級本質上是一個概率的問題,優先級越高,CPU調度的概率越高,并不具備確定性,如果出現問題,很難查找原因。當然,如果你很了解這些,并且就是為了性能和資源的考慮而做了優先級的選擇,那么你可以無視這些。
主隊列不需要參數可以直接獲取,不過主隊列并不會開啟新線程,主隊列上的所有任務都只會在主線程上執行,所以我們在平時的編程中,往往是在子線程中處理耗時操作,然后在主線程更新UI。在實際開發中,我們經常會遇到一種場景,就是在界面上顯示一張網絡圖片,要顯示圖片肯定得先下載,而下載是一個耗時操作,如果在主線程下載,那就會使界面卡住不能進行其它操作,所以我們一般都會在子線程下載,下載好以后再去主線程更新界面:
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(@"在子線程執行耗時操作!");
dispatch_async(mainQueue, ^{
NSLog(@"在主線程更新UI");
});
});
以上的使用可以滿足一些簡單業務的需求,但是實際開發中有很多復雜業務,比如說在用戶登錄的時候需要同步多種信息,而這些信息從不同的接口獲取,只有所有信息全部同步結束才可以正常操作,同步各種信息應該各自在子線程進行,我們可以異步執行并發隊列中的任務來做這些耗時操作,但是我們怎么知道所有任務都執行完了呢?
GCD Group
GCD為我們提供了另一個東西,叫做Group(調度組)。調度組是用來協調一個或多個任務提交到隊列異步觸發的。 應用程序可以使用調度組等待所有調度組中的所有任務的完成。
所有異步隊列執行完畢后得到一個通知。
調度組的使用并不復雜,它有兩種用法:
//創建一個調度組
dispatch_group_t group = dispatch_group_create();
//獲取全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//為隊列添加任務,并且和給定的調度組關聯
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");
});
//所有任務執行完畢通知
dispatch_group_notify(group, queue, ^{
NSLog(@"全部都完了");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"更新UI");
});
});
或者
//創建一個調度組
dispatch_group_t group = dispatch_group_create();
//獲取全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//手動添加一個任務到該調度組
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"同步信息1");
//該任務執行完畢從調度組移除
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);
});
//等待所有任務執行完畢 參數:1.對應的調度組 2.超時時間
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//所有任務執行完畢才會來這里
dispatch_async(queue, ^{
NSLog(@"全部都完了");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"更新UI");
});
});
運行結果:
兩種用法的運行結果是相同的,第二種更加靈活一些,但是代碼量也相應多一些,使用調度組我們可以更好的對任務進行控制,并且在特定的場景滿足我們的需求。靈活使用調度組,可以讓我們對線程同步控制更加得心應手。
GCD還有一個很重要的功能,就是一次執行。用這個代碼塊包含的代碼只會執行一次,在實際開發中經常使用,單例模式一般都會用GCD來做,因為它效率高:
for (int i = 0; i < 10; i++) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"你猜我會執行幾次?");
});
}
運行以后只會打印一次。
-
NSOperation
NSOperation
是對GCD面向對象的封裝。它擁有GCD的高效,也擁有面向對象的編程思想。和GCD類似,它也是把任務放在隊列里去執行,不過它比GCD少了一些概念,但是它也有了一些GCD沒有的功能,接下來我們就從最開始了解NSOperation
。
Operation翻譯過來是操作的意思,其實跟GCD的任務是一樣的。因為它本身就是GCD的封裝,所以在理解上也差不多。我們順著GCD的用法來使用NSOperation
。
NSOperation
本身是個抽象類,我們要使用它就得使用它的子類。系統給我們提供了兩個,分別是:NSInvocationOperation
和NSBlockOperation
。一種是通過selector的形式添加操作,一種是以block的形式添加操作,我個人更喜歡NSBlockOperation
,用起來更方便些。
分別用這兩種方式創建兩個操作(其實就是GCD的任務):
//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]);
}
根據GCD的操作流程,這時候就需要創建隊列了。NSOperation
的隊列也有一個類NSOperationQueue
,它的創建也很簡單:
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
然后把前面創建的操作添加到隊列中:
[operationQueue addOperation:blockOperation];
[operationQueue addOperation:invocationOperation];
這個地方也不需要指定它的執行方式,直接把操作添加到隊列中就會自動異步執行:
NSOperationQueue
本身也有通過block添加操作的方法,不需要我們專門去創建,這樣就進一步簡化了代碼:
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperationWithBlock:^{
NSLog(@"1%@",[NSThread currentThread]);
}];
[operationQueue addOperationWithBlock:^{
NSLog(@"2%@",[NSThread currentThread]);
}];
直接運行:
NSOperationQueue
并沒有全局隊列,但是我們可以自己根據需求創建全局隊列。NSOperationQueue
也有獲取主隊列的類方法[NSOperationQueue mainQueue];
,用起來也很簡單方便,跟GCD中的主隊列一樣。
一個子線程處理耗時操作,然后刷新UI的代碼,使用NSOperationQueue
的方式就變成了這樣:
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperationWithBlock:^{
NSLog(@"子線程處理耗時操作%@", [NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"主線程更新UI%@", [NSThread currentThread]);
}];
}];
運行結果:
怎么樣,是不是用起來很簡單。
在線程同步上,NSOperation
沒有group
,但是有操作依賴,一樣可以實現同樣的效果。它的依賴,是操作的方法,所以如果要使用依賴,我們就得自己創建操作,然后操作之間設置好依賴關系,再把它們丟到隊列里,比如說前面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個操作完成才能開始執行,運行結果:
這里要注意,操作之間一定不能形成循環依賴,循環依賴的任務沒辦法執行,因為都得再對方執行完畢之后才滿足自己的執行條件。
這里在給隊列添加操作的時候,用了新的方法[queue addOperations:@[op1,op2,op3,op4] waitUntilFinished:NO];
,這個方法可以一次性添加多個操作,但是后面有一個參數,看名字我們就可以知道,如果傳YES
,它會一直等所有任務都執行完畢才會繼續執行下面的任務,有點類似于GCD
里面group的那個wait
,但是它會卡主當前線程,所以不能在主線程中使用,我們也可以在子線程里面執行這一段代碼,稍加改造,達到同樣的效果:
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");
}];
運行結果是一樣的,這里就不貼圖了。
不知道你有沒有注意到,NSOperationQueue
本身并沒有并發隊列和串行隊列的選項,它默認是并發隊列,但是,它有一個maxConcurrentOperationCount
屬性(代表了最大并發數,也就是最多能夠開幾條線程執行操作),如果最大并發數量為1,它就變成了類似串行隊列的模樣。
NSOperationQueue
還可以使用suspended
屬性來控制隊列里操作的暫停和繼續。使用cancelAllOperations
方法來取消隊列里的所有操作。這些簡單的屬性和方法就不專門演示了。
-
總結
以上就是OC中的多線程,在實際開發中,我們幾乎不會使用pthread
,很少會使用NSThread
,不過NSThread
的一些類方法會經常使用,比如獲取當前線程,睡眠當前線程等。GCD
和NSOperation
使用起來都效率更高,并且操作簡單,是我們更好的選擇。它倆之間的選擇一般沒有明確的分界線,可以根據實際需求來選擇,不過一般中小型項目多使用GCD
,大型項目多使用NSOperation
,可能是因為GCD
更底層
,更輕
一些,而NSOperation
更規范
,同時也重
了些。
與本文相關的代碼點擊前往。