OC 常用的多線程實現(xiàn)方法有:
NSThread
GCD
NSOperation
下面逐一總結(jié)一下。
NSThread
NSThread
是官方提供的筷转,面向?qū)ο蟮膭?chuàng)建多線程的方法姑原。
NSThread可以隨時查看當(dāng)前代碼所在的線程。比如:
NSLog(@"%@", [NSThread currentThread]);
// <NSThread: 0x6000007f0000>{number = 1, name = main}
NSThread可以使用類方法快速創(chuàng)建子線程呜舒,但是得不到子線程對象锭汛,線程自動開啟。比如:
[NSThread detachNewThreadWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
// <NSThread: 0x60000288a9c0>{number = 7, name = (null)}
}];
NSThread可以使用對象方法創(chuàng)建子線程,能夠得到子線程對象唤殴,需要調(diào)用start
方法手動開啟子線程般婆。
self.thread = [[NSThread alloc] initWithBlock: ^{
NSLog(@"%@", [NSThread currentThread]);
// <NSThread: 0x60000031d980>{number = 7, name = my thread}
}];
self.thread.name = @"my thread";
[self.thread start];
NSThread可以結(jié)束子線程、可以隨時查看線程的狀態(tài)(正在執(zhí)行朵逝、被取消蔚袍、結(jié)束)。
self.thread = [[NSThread alloc] initWithBlock: ^{
NSLog(@"當(dāng)前線程%@", [NSThread currentThread]);
NSLog(@"isExecuting=%d", self.thread.isExecuting);
NSLog(@"isCancelled=%d", self.thread.isCancelled);
NSLog(@"isFinished=%d", self.thread.isFinished);
for (int i = 0; i < 100; i++) {
NSLog(@"i = %d", i);
if (i == 6) {
NSLog(@"結(jié)束線程%@", [NSThread currentThread]);
//結(jié)束線程
[NSThread exit];
NSLog(@"這行代碼不會打印的");
}
}
}];
self.thread.name = @"my thread";
[self.thread start];
[NSThread sleepForTimeInterval:2];
NSLog(@"isExecuting=%d", self.thread.isExecuting);
NSLog(@"isCancelled=%d", self.thread.isCancelled);
NSLog(@"isFinished=%d", self.thread.isFinished);
// 當(dāng)前線程<NSThread: 0x6000003ed740>{number = 7, name = my thread}
// isExecuting=1
// isCancelled=0
// isFinished=0
// i = 0
// i = 1
// i = 2
// i = 3
// i = 4
// i = 5
// i = 6
// 結(jié)束線程<NSThread: 0x6000003ed740>{number = 7, name = my thread}
// isExecuting=0
// isCancelled=0
// isFinished=1
我們可以看到配名,exit
方法可以終止線程的執(zhí)行啤咽,執(zhí)行exit
方法可以把isExecuting
從1設(shè)為0,把isFinished
從0設(shè)為1渠脉,而isCancelled
不受影響闰蚕,這說明cancel
只是一個屬性,并不能真正的取消當(dāng)前線程连舍。
GCD
GCD(Grand Central Dispatch)是蘋果官方開發(fā)的解決多線程的一種方案没陡。GCD引入了兩個核心概念,任務(wù)和隊列索赏,來抽象多線程的創(chuàng)建和調(diào)用盼玄。理解任務(wù)和隊列是正確使用GCD的關(guān)鍵。
任務(wù)又分為同步任務(wù)(sync)和異步任務(wù)(async):
- 同步任務(wù):把任務(wù)添加到當(dāng)前線程中潜腻,不開啟新的線程埃儿,在任務(wù)完成之前阻塞當(dāng)前線程,都是串行執(zhí)行任務(wù)
- 異步任務(wù):可以開啟新的線程融涣,任務(wù)可以添加到新的線程中童番,不阻塞當(dāng)前線程
隊列又分為串行隊列(serial)和并發(fā)隊列(concurrent):
- 串行隊列:在一個線程中,一個接一個的完成任務(wù)威鹿,先進先出(FIFO)
- 并發(fā)隊列:在一個或多個線程中剃斧,并發(fā)的完成任務(wù)(后添加的任務(wù)不用等待前添加的任務(wù)先完成)
基于以上分類,我們可以得出一些推論:
- 并發(fā)隊列只有在異步任務(wù)中才有效忽你,因為在同步任務(wù)中幼东,不能開啟新線程,并發(fā)隊列還是只能一個接一個的完成科雳,這就相當(dāng)于串行隊列
開發(fā)中常用的隊列有四種根蟹,分別是:
-
自定義串行隊列:當(dāng)前線程中建立的串行隊列,在當(dāng)前線程中糟秘,
dispatch_queue_create("CXqueue", DISPATCH_QUEUE_SERIAL);
-
自定義并發(fā)隊列:當(dāng)前線程中建立的并發(fā)隊列简逮,因為是并發(fā),所以可以在任意線程尿赚,
dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
-
全局隊列:全局中的并發(fā)隊列散庶,因為是并發(fā)蕉堰,所以可以在任意線程,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-
主隊列:主線程中的主隊列督赤,是串行隊列嘁灯,
dispatch_get_main_queue()
這里可以看出,自定義并發(fā)隊列和全局隊列的作用很相似躲舌。
下面討論一下不同任務(wù)和隊列和組合:
- 同步任務(wù)+自定義串行隊列
- 同步任務(wù)+自定義并發(fā)隊列
- 異步任務(wù)+自定義串行隊列
- 異步任務(wù)+自定義并發(fā)隊列
- 同步任務(wù)+主隊列
- 異步任務(wù)+主隊列
- 同步任務(wù)+全局隊列
- 異步任務(wù)+全局隊列
同步任務(wù)+自定義串行隊列:
dispatch_queue_t q = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
NSLog(@"開始");
dispatch_sync(q, ^{
NSLog(@"任務(wù)一");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_sync(q, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)二丑婿,睡了2秒");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_sync(q, ^{
NSLog(@"任務(wù)三");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
NSLog(@"結(jié)束");
// 開始
// 任務(wù)一
// 當(dāng)前線程:<NSThread: 0x6000039dc100>{number = 1, name = main}
// 任務(wù)二,睡了2秒
// 當(dāng)前線程:<NSThread: 0x6000039dc100>{number = 1, name = main}
// 任務(wù)三
// 當(dāng)前線程:<NSThread: 0x6000039dc100>{number = 1, name = main}
// 結(jié)束
可以看到没卸,任務(wù)一羹奉,二,三约计,一個接一個的完成了诀拭,而且都在線程1中(主線程main
)。
同步任務(wù)+自定義并行隊列:
dispatch_queue_t q = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"開始");
dispatch_sync(q, ^{
NSLog(@"任務(wù)一");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_sync(q, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)二煤蚌,睡了2秒");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_sync(q, ^{
NSLog(@"任務(wù)三");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
NSLog(@"結(jié)束");
// 開始
// 任務(wù)一
// 當(dāng)前線程:<NSThread: 0x600000380980>{number = 1, name = main}
// 任務(wù)二耕挨,睡了2秒
// 當(dāng)前線程:<NSThread: 0x600000380980>{number = 1, name = main}
// 任務(wù)三
// 當(dāng)前線程:<NSThread: 0x600000380980>{number = 1, name = main}
// 結(jié)束
可以看到,任務(wù)一尉桩,二筒占,三,一個接一個的完成了蜘犁,而且都在線程1中翰苫,這說明并發(fā)隊列在同步任務(wù)中相當(dāng)于串行隊列。
異步任務(wù)+自定義串行隊列:
dispatch_queue_t q = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
NSLog(@"開始");
dispatch_async(q, ^{
NSLog(@"任務(wù)一");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_async(q, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)二这橙,睡了2秒");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_async(q, ^{
NSLog(@"任務(wù)三");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
NSLog(@"結(jié)束");
// 當(dāng)前線程:<NSThread: 0x60000357c000>{number = 1, name = main}
// 開始
// 結(jié)束
// 任務(wù)一
// 當(dāng)前線程:<NSThread: 0x600003545c40>{number = 6, name = (null)}
// 任務(wù)二奏窑,睡了2秒
// 當(dāng)前線程:<NSThread: 0x600003545c40>{number = 6, name = (null)}
// 任務(wù)三
// 當(dāng)前線程:<NSThread: 0x600003545c40>{number = 6, name = (null)}
可以看到,任務(wù)一屈扎,二埃唯,三是一個接一個完成,的確是串行的助隧,但他們都被放到了線程6去執(zhí)行了筑凫,這是因為異步任務(wù)的原因,還是就是打印開始之后并村,馬上就是打印結(jié)束,這說明異步任務(wù)的創(chuàng)建需要時間滓技,異步任務(wù)開始前哩牍,主線程已經(jīng)完成打印了
異步任務(wù)+自定義并行隊列:
dispatch_queue_t q = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
NSLog(@"開始");
dispatch_async(q, ^{
NSLog(@"任務(wù)一");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_async(q, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)二,睡了2秒");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_async(q, ^{
NSLog(@"任務(wù)三");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
NSLog(@"結(jié)束");
// 當(dāng)前線程:<NSThread: 0x600003cd8300>{number = 1, name = main}
// 開始
// 結(jié)束
// 任務(wù)一
// 任務(wù)三
// 當(dāng)前線程:<NSThread: 0x600003cea3c0>{number = 3, name = (null)}
// 當(dāng)前線程:<NSThread: 0x600003cc0e40>{number = 6, name = (null)}
// 任務(wù)二令漂,睡了2秒
// 當(dāng)前線程:<NSThread: 0x600003cff880>{number = 5, name = (null)}
可以看到膝昆,任務(wù)一丸边,二,三并放到了三個不同的線程中去執(zhí)行荚孵,并且執(zhí)行的時候是并發(fā)的(不是一個接一個的完成)
同步任務(wù)+主隊列(重點來了)
dispatch_queue_t q = dispatch_get_main_queue();
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
NSLog(@"開始");
dispatch_sync(q, ^{ // Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
NSLog(@"任務(wù)一");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_sync(q, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)二妹窖,睡了2秒");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_sync(q, ^{
NSLog(@"任務(wù)三");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
NSLog(@"結(jié)束");
奔潰了,原因是發(fā)生了死鎖收叶,為什么呢骄呼?
我們就拿任務(wù)一進行分析。
因為在主線程中判没,dispatch_sync
也是一個任務(wù)蜓萄,因為阻塞線程,現(xiàn)在被添加到了主隊列澄峰,dispatch_sync
中的任務(wù)(任務(wù)一)也添加到了主隊列嫉沽,任務(wù)一完成后dispatch_sync
才能返回(完成),而按照串行隊列的定義俏竞,dispatch_sync
完成后绸硕,任務(wù)一才能執(zhí)行,因此魂毁,dispatch_sync與任務(wù)一之間創(chuàng)造了死鎖玻佩。
同樣都是串行隊列,為什么主隊列會死鎖漱牵,而自定義串行隊列不會呢夺蛇?
因為如果是自定義串行隊列,dispatch_sync
中的任務(wù)(任務(wù)一)被添加到自定義的隊列中酣胀,這樣任務(wù)一在自定義串行隊列中刁赦,不用等待dispatch_sync
完成才開始執(zhí)行,自己就執(zhí)行了闻镶,dispatch_sync
等任務(wù)一完成后甚脉,就返回了,所以沒有造成死鎖铆农。
異步任務(wù)+主隊列
dispatch_queue_t q = dispatch_get_main_queue();
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
NSLog(@"開始");
dispatch_async(q, ^{
NSLog(@"任務(wù)一");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_async(q, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)二牺氨,睡了2秒");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_async(q, ^{
NSLog(@"任務(wù)三");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
NSLog(@"結(jié)束");
// 當(dāng)前線程:<NSThread: 0x6000017e8900>{number = 1, name = main}
// 開始
// 結(jié)束
// 任務(wù)一
// 當(dāng)前線程:<NSThread: 0x6000017e8900>{number = 1, name = main}
// 任務(wù)二,睡了2秒
// 當(dāng)前線程:<NSThread: 0x6000017e8900>{number = 1, name = main}
// 任務(wù)三
// 當(dāng)前線程:<NSThread: 0x6000017e8900>{number = 1, name = main}
可以看到墩剖,因為是同步任務(wù)猴凹,任務(wù)一,二岭皂,三還是按照串行的方式執(zhí)行的郊霎,而且都在主線程中執(zhí)行,因為dispatch_async
不阻塞當(dāng)前線程(不用等待里面的任務(wù)完成之后才返回)爷绘,所以不造成死鎖书劝,同時我們注意到进倍,異步任務(wù)沒有開啟新的線程,這和異步任務(wù)+自定義串行隊列有區(qū)別购对。
同步任務(wù)+全局隊列
與同步任務(wù)+自定義并行隊列結(jié)果一樣
異步任務(wù)+全局隊列
與異步任務(wù)+自定義并行隊列結(jié)果一樣
總結(jié):
-
GCD的使用步驟:
- 創(chuàng)建或者獲取隊列(串行猾昆、并發(fā),主隊列骡苞,全局隊列)
- 創(chuàng)建任務(wù)(同步垂蜗、異步)
- 把任務(wù)放在隊列中執(zhí)行
同步任務(wù)+自定義串行隊列:不開啟新線程,串行執(zhí)行
同步任務(wù)+自定義并發(fā)隊列:不開啟新線程烙如,串行執(zhí)行
異步任務(wù)+自定義串行隊列:開啟新線程么抗,串行執(zhí)行
異步任務(wù)+自定義并發(fā)隊列:開啟新線程,并行執(zhí)行
同步任務(wù)+主隊列:死鎖
異步任務(wù)+主隊列:不開啟新線程亚铁,串行執(zhí)行
同步任務(wù)+全局隊列:開啟新線程蝇刀,串行執(zhí)行
異步任務(wù)+全局隊列:開啟新線程,并行執(zhí)行
NSOperation
NSOperation
的使用和GCD類似徘溢,也是三步:
- 將任務(wù)封裝到
NSOperation
對象中 - 將
NSOperation
對象添加到NSOperationQueue
中 - 系統(tǒng)將
NSOperationQueue
中的NSOperation
中的任務(wù)取出來放到一條新線程中執(zhí)行吞琐。
NSOperation
是一個抽象類,不具備封裝操作的能力然爆,必須使用它的子類站粟,他的子類有:NSInvocationOperation
, NSBlockOperation
, 或者自定義子類。
NSInvocationOperation
可以直接start
任務(wù)(在主線程):
- (void)viewDidLoad {
[super viewDidLoad];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
[operation start];
}
- (void)test {
NSLog(@"----任務(wù)開始----");
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
}
// ----任務(wù)開始----
// 當(dāng)前線程:<NSThread: 0x600002f2c3c0>{number = 1, name = main}
NSBlockOperation
可以直接start
任務(wù)(在主線程)曾雕,還可以追加任務(wù)奴烙,追加的任務(wù)在子線程(是并行的):
- (void)viewDidLoad {
[super viewDidLoad];
// NSBlockOperation
NSBlockOperation * operation = [NSBlockOperation blockOperationWithBlock:^{
// 要執(zhí)行的操作,目前是主線程
NSLog(@"NSBlockOperation 創(chuàng)建剖张,當(dāng)前線程:%@",[NSThread currentThread]);
}];
// 追加任務(wù)切诀,在子線程中執(zhí)行
[operation addExecutionBlock:^{
NSLog(@"追加任務(wù)一");
[self test];
}];
[operation addExecutionBlock:^{
NSLog(@"追加任務(wù)二, %@",[NSThread currentThread]);
}];
[operation start];
}
- (void)test {
NSLog(@"----任務(wù)開始----");
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
}
// 追加任務(wù)一
// NSBlockOperation 創(chuàng)建,當(dāng)前線程:<NSThread: 0x600003af4380>{number = 1, name = main}
// 追加任務(wù)二, <NSThread: 0x600003aeaa40>{number = 3, name = (null)}
// ----任務(wù)開始----
// 當(dāng)前線程:<NSThread: 0x600003aa9000>{number = 6, name = (null)}
自定義子類:
@interface BZOperation : NSOperation
@end
@implementation BZOperation
-(void) main {
NSLog(@"我是自定義NSOperation搔弄,當(dāng)前線程:%@", [NSThread currentThread]);
}
@end
BZOperation * operation = [[BZOperation alloc] init];
[operation start];
// 我是自定義NSOperation幅虑,當(dāng)前線程:<NSThread: 0x600002a64440>{number = 1, name = main}
下面總結(jié)NSOperationQueue
NSOperationQueue
相當(dāng)于GCD中的隊列,將NSOperation
加入到NSOperationQueue
后顾犹,就自動執(zhí)行NSOperation
中的任務(wù)了倒庵,不需要執(zhí)行start
方法。
主要有兩種隊列:
- 主隊列:在主隊列中的任務(wù)都在主線程中執(zhí)行炫刷。
- 非主隊列:非主隊列中的任務(wù)可以在其他線程中執(zhí)行擎宝。
添加NSOperation
到NSOperationQueue
的方法主要有兩種:
-(void)addOperation:(NSOperation *)op;
-(void)addOperationWithBlock:(void (^)(void))block;
下面我們嘗試一下不同的組合:
主隊列+NSBlockOperation
//1.創(chuàng)建主隊列
NSOperationQueue *q1 = [NSOperationQueue mainQueue];
//2.創(chuàng)建任務(wù)
NSBlockOperation *p1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)一,當(dāng)前線程:%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)一結(jié)束");
}];
NSBlockOperation *p2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)二浑玛,當(dāng)前線程:%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)二結(jié)束");
}];
NSBlockOperation *p3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)三认臊,當(dāng)前線程:%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)三結(jié)束");
}];
//3.把任務(wù)添加到隊列
[q1 addOperation:p1];
[q1 addOperation:p2];
[q1 addOperation:p3];
// 任務(wù)一,當(dāng)前線程:<NSThread: 0x60000118c000>{number = 1, name = main}
// 任務(wù)一結(jié)束
// 任務(wù)二锄奢,當(dāng)前線程:<NSThread: 0x60000118c000>{number = 1, name = main}
// 任務(wù)二結(jié)束
// 任務(wù)三失晴,當(dāng)前線程:<NSThread: 0x60000118c000>{number = 1, name = main}
// 任務(wù)三結(jié)束
可以看出,任務(wù)全都在主線程中串行執(zhí)行的拘央。
非主隊列+NSBlockOperation
//1.創(chuàng)建非主隊列
NSOperationQueue *q1 = [[NSOperationQueue alloc] init];
//2.創(chuàng)建任務(wù)
NSBlockOperation *p1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)一涂屁,當(dāng)前線程:%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)一結(jié)束");
}];
NSBlockOperation *p2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)二,當(dāng)前線程:%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)二結(jié)束");
}];
NSBlockOperation *p3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)三灰伟,當(dāng)前線程:%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)三結(jié)束");
}];
//3.把任務(wù)添加到隊列
[q1 addOperation:p1];
[q1 addOperation:p2];
[q1 addOperation:p3];
// 任務(wù)三拆又,當(dāng)前線程:<NSThread: 0x600001a66d80>{number = 6, name = (null)}
// 任務(wù)二,當(dāng)前線程:<NSThread: 0x600001a55800>{number = 4, name = (null)}
// 任務(wù)一栏账,當(dāng)前線程:<NSThread: 0x600001a5e000>{number = 3, name = (null)}
// 任務(wù)二結(jié)束
// 任務(wù)一結(jié)束
// 任務(wù)三結(jié)束
可以看到帖族,任務(wù)是在不同線程中,并發(fā)完成的挡爵。