iOS 多線程有幾種方式
- GCD
- NSOpeartion
- NSThread
- phread
多線程
GCD
-
dispatch_once_t
+ (TestModel *)shared { static TestModel *model; static dispatch_once_t once; dispatch_once(&once, ^{ model = [[TestModel alloc] init]; }); return model; }
-
dispatch_group
- (void)dispatchGroup { dispatch_queue_t queue = dispatch_queue_create("groupTest", DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ NSLog(@"任務(wù)1 ready"); sleep(2); NSLog(@"任務(wù)1 完成"); }); dispatch_group_enter(group); dispatch_async(queue, ^{ NSLog(@"任務(wù)2 ready"); sleep(4); NSLog(@"任務(wù)2 完成"); dispatch_group_leave(group); }); dispatch_group_notify(group, queue, ^{ NSLog(@"group 任務(wù)完成"); }); }
- dispatch_group_notify 可以添加多次领铐,并會(huì)多次調(diào)用
- 只要 group 中的任務(wù)沒有完成,group 完成的監(jiān)聽就不會(huì)被調(diào)用稿饰,即使是后追加的任務(wù)
- notify 方法中第二個(gè) queue 的參數(shù)決定了 callBack 將會(huì)在那個(gè)隊(duì)列執(zhí)行
-
dispatch_apply
多線程快速遍歷本質(zhì)是
dispatch_sync
與dispatch_group
關(guān)聯(lián)的 api,因?yàn)樵摲椒〞?huì)等待內(nèi)部所有操作都結(jié)束再返回,內(nèi)部操作是否同步依賴傳入的queue寥掐,外部必定是同步的最岗。如果有需要,需將該方法放到一個(gè)異步的并行隊(duì)列中如果傳入多線程荆永,輸出的下標(biāo)未必按照順序執(zhí)行
- (void)dispatchApply { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(10, queue, ^(size_t index) { NSLog(@"%@------%d", NSThread.currentThread, index); sleep(1); }); NSLog(@"111111"); } // "111111" 必定出現(xiàn)在最后
-
定時(shí)器
DispatchSourceTimer
不同于基于 Runloop 的
NSTimer
废亭,DispatchSourceTimer
不會(huì)因?yàn)樽泳€程沒有正在運(yùn)行的 Runloop 而失效,也不會(huì)有循環(huán)引用具钥、計(jì)時(shí)不準(zhǔn)(每次 runloop 循環(huán)才會(huì)檢查定時(shí)器是否需要被執(zhí)行)等問題豆村。但有幾點(diǎn)需要注意:- suspend 與 resume 一定要成對(duì)使用,否則會(huì) crash
- timer 最好被持有骂删,否則在 suspend 時(shí)可能 crash
var timer: DispatchSourceTimer? var index: Int = 0 func startTimer() { timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.global()) timer?.schedule(deadline: .now(), repeating: 0.1) timer?.setEventHandler(handler: { print("index: \(index)") index += 1 if index == 10 { cancelTimer() } }) timer?.resume() } private func cancelTimer() { timer?.cancel() timer = nil }
-
信號(hào)量
DispatchSemaphore
信號(hào)量類似于鎖掌动,信號(hào)量為 0 則阻塞線程,大于 0 則不會(huì)阻塞宁玫。因此可以通過改變信號(hào)量的值來控制是否阻塞線程
-
DispatchSemaphore(value: 0)
初始化 -
semaphore.signal()
// 信號(hào)量 +1 -
semaphore.wait()
在信號(hào)量大于 0 的前提下粗恢,信號(hào)量 -1,如果信號(hào)量本來為 0欧瘪,則線程休眠眷射,加入到等待這個(gè)信號(hào)的線程隊(duì)列當(dāng)中。當(dāng)信號(hào)量大于 0 時(shí)佛掖,就會(huì)喚醒這個(gè)等待隊(duì)列中靠前的線程妖碉,繼續(xù)線程后面的代碼且對(duì)信號(hào)量減 1,也就確保了信號(hào)量大于 0 才減 1苦囱,所以不存在信號(hào)量小于 0 的情況(除非在初始化時(shí)設(shè)置為負(fù)數(shù)嗅绸,不過這樣做應(yīng)用程序會(huì) crash)。
// 下載兩個(gè)圖片后執(zhí)行操作 A // 1. 初始化信號(hào)量為 0 var seamphore = DispatchSemaphore(value: -0) // 2. 操作 A 前添加兩個(gè) wait 操作 seamphore.wait() seamphore.wait() // 3. 在每個(gè)下載結(jié)果回調(diào)中添加 seamphore.signal()
-
-
線程?hào)艡?
dispatch_barrier
線程?hào)艡诳梢宰枞硞€(gè) queue(必須是自定義的并行 queue撕彤,如果是 global 鱼鸠,則不會(huì)有預(yù)期效果)中任務(wù)的執(zhí)行直到 queue 中柵欄之前的任務(wù)執(zhí)行完畢
- (void)dispatchBarrier { dispatch_queue_t queue = dispatch_queue_create("aaa", DISPATCH_QUEUE_CONCURRENT); for (int i = 1; i <= 3; i ++) { dispatch_async(queue, ^{ sleep(1); NSLog(@"%d 任務(wù)結(jié)束", i); }); } NSLog(@"柵欄前面"); dispatch_barrier_sync(queue, ^{ sleep(3); NSLog(@"柵欄結(jié)束"); }); NSLog(@"柵欄后面"); for (int i = 4; i <= 6; i ++) { dispatch_async(queue, ^{ sleep(1); NSLog(@"%d 任務(wù)結(jié)束", i); }); } NSLog(@"代碼結(jié)束"); }
運(yùn)行結(jié)果
dispatch_barrier_sync 運(yùn)行結(jié)果 2020-07-24 20:17:09.905062+0800 ObjcTest[13685:969878] 柵欄前面 2020-07-24 20:17:10.907255+0800 ObjcTest[13685:969907] 2 任務(wù)結(jié)束 2020-07-24 20:17:10.907310+0800 ObjcTest[13685:969908] 1 任務(wù)結(jié)束 2020-07-24 20:17:10.907579+0800 ObjcTest[13685:969906] 3 任務(wù)結(jié)束 2020-07-24 20:17:13.907888+0800 ObjcTest[13685:969878] 柵欄結(jié)束 2020-07-24 20:17:13.908349+0800 ObjcTest[13685:969878] 柵欄后面 2020-07-24 20:17:13.908954+0800 ObjcTest[13685:969878] 代碼結(jié)束 2020-07-24 20:17:14.913851+0800 ObjcTest[13685:969906] 4 任務(wù)結(jié)束 2020-07-24 20:17:14.914364+0800 ObjcTest[13685:969908] 5 任務(wù)結(jié)束 2020-07-24 20:17:14.914628+0800 ObjcTest[13685:969907] 6 任務(wù)結(jié)束 dispatch_barrier_async 運(yùn)行結(jié)果 2020-07-24 20:18:16.955921+0800 ObjcTest[13691:970322] 柵欄前面 2020-07-24 20:18:16.956002+0800 ObjcTest[13691:970322] 柵欄后面 2020-07-24 20:18:16.956040+0800 ObjcTest[13691:970322] 代碼結(jié)束 2020-07-24 20:18:17.961793+0800 ObjcTest[13691:970352] 1 任務(wù)結(jié)束 2020-07-24 20:18:17.962022+0800 ObjcTest[13691:970353] 2 任務(wù)結(jié)束 2020-07-24 20:18:17.963561+0800 ObjcTest[13691:970357] 3 任務(wù)結(jié)束 2020-07-24 20:18:20.967381+0800 ObjcTest[13691:970357] 柵欄結(jié)束 2020-07-24 20:18:21.974364+0800 ObjcTest[13691:970357] 4 任務(wù)結(jié)束 2020-07-24 20:18:21.974901+0800 ObjcTest[13691:970353] 5 任務(wù)結(jié)束 2020-07-24 20:18:21.975174+0800 ObjcTest[13691:970352] 6 任務(wù)結(jié)束
dispatch_barrier_sync
與dispatch_barrier_async
區(qū)別為- 同步柵欄會(huì)阻塞之后的普通代碼的執(zhí)行猛拴,異步柵欄則不會(huì)
應(yīng)用線程?hào)艡诘奶匦裕梢愿玫淖鲆恍┚€程同步蚀狰。例如
任務(wù) 1愉昆、2、3結(jié)束后需要執(zhí)行任務(wù) 4麻蹋、5跛溉、6
如果用 dispatch_group ,則將任務(wù) 1扮授、2芳室、3 添加到 group 中,在 dispatch_group_notify 中執(zhí)行任務(wù) 4刹勃、5堪侯、6
NSOperation
NSOperation 是蘋果 GCD 面向?qū)ο蟮姆庋b
優(yōu)點(diǎn):
添加操作間的依賴關(guān)系,控制執(zhí)行順序
可以方便的取消一個(gè)操作的執(zhí)行
設(shè)定操作的優(yōu)先級(jí)
使用 KVO 觀察操作執(zhí)行狀態(tài)的更改
-
NSOperation 執(zhí)行的方式
- 添加到 NSOperationQueue荔仁,系統(tǒng)會(huì)自動(dòng)將 NSOperationQueue 中的 NSOperation 取出來伍宦,在線程中執(zhí)行操作
- 如果不顯式將 NSOperation 添加到 NSOperationQueue 中,也可以調(diào)用 operation.start() 方法乏梁,操作會(huì)在當(dāng)前線程中執(zhí)行
備注:使用
start
方法時(shí)次洼,如果 NSBlockOperation 調(diào)用addExecutionBlock:
方法,添加額外的操作遇骑,包括blockOperationWithBlock
中的操作在內(nèi)的這些操作可能在不同的線程中并發(fā)執(zhí)行卖毁,具體由系統(tǒng)決定NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 3; i ++) { sleep(2); NSLog(@"1---%@", NSThread.currentThread); } }]; [op addExecutionBlock:^{ for (int i = 0; i < 3; i ++) { sleep(2); NSLog(@"2---%@", NSThread.currentThread); } }]; [op addExecutionBlock:^{ for (int i = 0; i < 3; i ++) { sleep(2); NSLog(@"3---%@", NSThread.currentThread); } }]; [op addExecutionBlock:^{ for (int i = 0; i < 3; i ++) { sleep(2); NSLog(@"4---%@", NSThread.currentThread); } }]; [op addExecutionBlock:^{ for (int i = 0; i < 3; i ++) { sleep(2); NSLog(@"5---%@", NSThread.currentThread); } }]; [op addExecutionBlock:^{ for (int i = 0; i < 3; i ++) { sleep(2); NSLog(@"6---%@", NSThread.currentThread); } }]; [op start];
2020-07-27 14:19:25.237520+0800 ObjcTest[19476:1527007] 1---<NSThread: 0x282870ac0>{number = 5, name = (null)} 2020-07-27 14:19:25.237532+0800 ObjcTest[19476:1526976] 2---<NSThread: 0x28283da80>{number = 1, name = main} 2020-07-27 14:19:27.239170+0800 ObjcTest[19476:1526976] 2---<NSThread: 0x28283da80>{number = 1, name = main} 2020-07-27 14:19:27.239164+0800 ObjcTest[19476:1527007] 1---<NSThread: 0x282870ac0>{number = 5, name = (null)} 2020-07-27 14:19:29.240627+0800 ObjcTest[19476:1526976] 2---<NSThread: 0x28283da80>{number = 1, name = main} 2020-07-27 14:19:29.240620+0800 ObjcTest[19476:1527007] 1---<NSThread: 0x282870ac0>{number = 5, name = (null)} 2020-07-27 14:19:31.242202+0800 ObjcTest[19476:1527007] 4---<NSThread: 0x282870ac0>{number = 5, name = (null)}
從結(jié)果中可以看出
blockOperationWithBlock
中的代碼不是在主線程中執(zhí)行的,并且系統(tǒng)一共開啟了兩個(gè)線程執(zhí)行該 Operation -
NSOperationQueue 控制串行质蕉、并發(fā)
maxConcurrentOperationCount
屬性可以控制一個(gè)隊(duì)列中可以有多少個(gè)操作同時(shí)參與并發(fā)執(zhí)行注意:這里的
maxConcurrentOperationCount
控制的不是并發(fā)線程的數(shù)量势篡,而是一個(gè)隊(duì)列中同時(shí)能并發(fā)執(zhí)行的最大操作數(shù)。而且一個(gè)操作也并非只能在一個(gè)線程中運(yùn)行模暗。 -
NSOperation 操作依賴
NSOperation 可以添加操作之間的依賴禁悠。因此我們可以很方便的控制操作之間的執(zhí)行先后順序。并且一個(gè)操作可以添加多個(gè)依賴兑宇。
例如我們有如下需求:A碍侦、B兩個(gè)操作,A 執(zhí)行完畢隶糕,B 才能執(zhí)行操作瓷产。
// 1.創(chuàng)建隊(duì)列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.創(chuàng)建操作 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程 } }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程 } }]; // 3.添加依賴 [op2 addDependency:op1]; // 讓op2 依賴于 op1,則先執(zhí)行op1枚驻,在執(zhí)行op2 // 4.添加操作到隊(duì)列中 [queue addOperation:op1]; [queue addOperation:op2];
輸出如下
2020-07-27 14:43:35.998300+0800 ObjcTest[19617:1533985] 1---<NSThread: 0x281496200>{number = 6, name = (null)} 2020-07-27 14:43:38.000611+0800 ObjcTest[19617:1533985] 1---<NSThread: 0x281496200>{number = 6, name = (null)} 2020-07-27 14:43:40.007131+0800 ObjcTest[19617:1533985] 2---<NSThread: 0x281496200>{number = 6, name = (null)} 2020-07-27 14:43:42.021944+0800 ObjcTest[19617:1533985] 2---<NSThread: 0x281496200>{number = 6, name = (null)}
可以看出濒旦,op1 先執(zhí)行完畢后,op2 才開始執(zhí)行
-
NSOperation 優(yōu)先級(jí)
優(yōu)先級(jí)只體現(xiàn)在兩個(gè)時(shí)間點(diǎn)
- 依賴任務(wù)處理完成再登、隊(duì)列對(duì)后續(xù)任務(wù)的調(diào)度
- 依賴隊(duì)列從暫停轉(zhuǎn)變?yōu)橹匦聠?dòng)尔邓、后續(xù)任務(wù)的調(diào)度
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1--- 開始執(zhí)行, %@", NSThread.currentThread); sleep(2); NSLog(@"1--- 執(zhí)行完畢"); }]; op1.queuePriority = NSOperationQueuePriorityLow; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"2--- 開始執(zhí)行, %@", NSThread.currentThread); sleep(2); NSLog(@"2--- 執(zhí)行完畢"); }]; op2.queuePriority = NSOperationQueuePriorityHigh; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"依賴--- 開始執(zhí)行, %@", NSThread.currentThread); sleep(2); NSLog(@"依賴--- 執(zhí)行完畢"); }]; [op1 addDependency:op3]; [op2 addDependency:op3]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; queue.maxConcurrentOperationCount = 1; [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3];
結(jié)果如下
2020-07-27 15:13:05.623639+0800 ObjcTest[19750:1540177] 依賴--- 開始執(zhí)行, <NSThread: 0x281561c00>{number = 5, name = (null)} 2020-07-27 15:13:07.629611+0800 ObjcTest[19750:1540177] 依賴--- 執(zhí)行完畢 2020-07-27 15:13:07.630799+0800 ObjcTest[19750:1540177] 2--- 開始執(zhí)行, <NSThread: 0x281561c00>{number = 5, name = (null)} 2020-07-27 15:13:09.631681+0800 ObjcTest[19750:1540177] 2--- 執(zhí)行完畢 2020-07-27 15:13:09.632164+0800 ObjcTest[19750:1540176] 1--- 開始執(zhí)行, <NSThread: 0x281561380>{number = 3, name = (null)} 2020-07-27 15:13:11.637458+0800 ObjcTest[19750:1540176] 1--- 執(zhí)行完畢
-
NSOperationQueue 暫停和取消
注意:
- 這里的暫停和取消(包括操作的取消和隊(duì)列的取消)并不代表可以將當(dāng)前的操作立即取消晾剖,而是當(dāng)當(dāng)前的操作執(zhí)行完畢后不再執(zhí)行新的操作
- 暫停和取消的區(qū)別在于:暫停之后還可以恢復(fù)操作,繼續(xù)向下執(zhí)行梯嗽;而取消操作之后齿尽,所有的操作就清空了,不再執(zhí)行剩下的操作
NSThread
NSThread 在代碼中偶爾會(huì)使用灯节,例如 [NSThread currentThread]
-
創(chuàng)建循头、啟動(dòng)線程
- (void)threadTest { NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [thread start]; // 創(chuàng)建線程后自動(dòng)啟動(dòng)線程 //[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; // 隱式創(chuàng)建并啟動(dòng)線程 //[self performSelectorInBackground:@selector(run) withObject:nil]; } - (void)run { NSLog(@"currentThread: %@", NSThread.currentThread); }
-
線程狀態(tài)控制
+ (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti;// 線程進(jìn)入阻塞狀態(tài) + (void)exit; // 線程 kill
-
線程間通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
-
與 runloop 關(guān)系
- (void)run { NSLog(@"currentThread: %@", NSThread.currentThread); [self performSelector:@selector(threadAfter) withObject:nil afterDelay:1]; } - (void)threadAfter { NSLog(@"1111111"); }
上述代碼默認(rèn)不會(huì)起作用,‘1111111’ 不會(huì)被打印出來炎疆。
performSelector: withObject: afterDelay:
方法默認(rèn)會(huì)創(chuàng)建一個(gè) timer 添加到當(dāng)前 runloop 中卡骂,而子線程默認(rèn)不開啟 runloop,因此上述代碼不起作用形入。
解決方法是在 performSelector 方法后面添加[[NSRunLoop currentRunLoop] run];
如果將 runloop 啟動(dòng)的代碼放到前面偿警,仍然不會(huì)起作用,原因是 runloop 啟動(dòng)后沒有可執(zhí)行的代碼唯笙,會(huì)立刻退出,此時(shí)再添加 timer 也沒有什么作用盒使。
pthread
pthread 是一套通用的多線程 API崩掘,使用 C 語言編寫,需要程序員自己管理線程的生命周期少办,使用難度較大苞慢,很少使用
-
pthread_create()
創(chuàng)建一個(gè)線程 -
pthread_exit()
終止當(dāng)前線程 -
pthread_cancel()
中斷另外一個(gè)線程的運(yùn)行 -
pthread_join()
阻塞當(dāng)前的線程哀墓,直到另外一個(gè)線程運(yùn)行結(jié)束 -
pthread_attr_init()
初始化線程的屬性 -
pthread_attr_setdetachstate()
設(shè)置脫離狀態(tài)的屬性(決定這個(gè)線程在終止時(shí)是否可以被結(jié)合) -
pthread_attr_getdetachstate()
獲取脫離狀態(tài)的屬性 -
pthread_attr_destroy()
刪除線程的屬性 -
pthread_kill()
向線程發(fā)送一個(gè)信號(hào)
問題
- 子線程同時(shí)執(zhí)行 ABC 三個(gè)同步任務(wù)筐高,全部執(zhí)行完畢后再在自線程執(zhí)行三個(gè)同步任務(wù) EDF,應(yīng)該怎樣做骡技?
- 可以使用 GCD 的group 或者 NSOperation 的 依賴
- 可以使用 dispatch_barrier 稍簡(jiǎn)單一些
- 將問題 1 中的 ABC 三個(gè)任務(wù)改為異步任務(wù)如 AFN 網(wǎng)絡(luò)請(qǐng)求蔓纠,全部回調(diào)成功后進(jìn)行數(shù)據(jù)整合辑畦,應(yīng)該怎樣做?
- 使用信號(hào)量
- 使用 GCD 的 group 也可以腿倚。
dispatch_group_enter
與dispatch_group_leave
搭配使用
- 線程的生命周期是怎樣的纯出?
可參考 線程的生命周期以及常駐線程