一辈赋、前言
多線程是在 iOS 里非常重要的一塊兒知識點,我最近學(xué)習(xí)了李明杰大神的多線程相關(guān)視頻冀墨,對自己的多線程相關(guān)知識進(jìn)行了查缺補(bǔ)漏,受益良多涛贯,在此根據(jù)所學(xué)進(jìn)行簡單的記錄诽嘉,希望能幫助更多伙伴,也能作為自己模糊了以后查閱使用。
本篇文章分為上下兩篇虫腋。
二骄酗、iOS中常見的多線程方案
技術(shù)方案 | 簡介 | 語言 | 線程生命周期 | 使用頻率 |
---|---|---|---|---|
pthread | ? 一套通用的多線程API ? 適用于Unix\Linux\Windows等系統(tǒng) ? 跨平臺\可移植 ? 使用難度大 |
C | 程序員管理 | 幾乎不用 |
NSThread | ? 使用更加面向?qū)ο?br>? 簡單易用,可直接操作線程對象 | OC | 程序員管理 | 偶爾使用 |
GCD | ? 旨在替代NSThread等線程技術(shù) ? 充分利用設(shè)備的多核 |
C | 自動管理 | |
NSOperation | ? 基于GCD(底層是GCD) ? 比GCD多了一些更簡單實用的功能 ? 使用更加面向?qū)ο?/td> | OC | 自動管理 |
二悦冀、同步趋翻、異步、串行盒蟆、并發(fā)
1踏烙、GCD 中有 2 個用來執(zhí)行任務(wù)的函數(shù)
- 用同步的方式執(zhí)行任務(wù)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
? queue:隊列
? block:任務(wù)
2、GCD 的隊列可以分為 2 大類型
隊列(Concurrent Dispatch Queue)
? 可以讓多個任務(wù)(同時)執(zhí)行(自動開啟多個線程同時執(zhí)行任務(wù))
?功能只有在
(dispatch_
)函數(shù)下才有效
隊列(Serial Dispatch Queue)
? 讓任務(wù)一個接著一個地執(zhí)行(一個任務(wù)執(zhí)行完畢后历等,再執(zhí)行下一個任務(wù))用異步的方式執(zhí)行任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
3讨惩、容易混淆的術(shù)語
有 4 個術(shù)語比較容易混淆:
寒屯、
和
主要影響:能不能開啟新的線程
?:在
線程中執(zhí)行任務(wù),
開啟新線程的能力
?:在
線程中執(zhí)行任務(wù)寡夹,
開啟新線程的能力
并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
?:
任務(wù)并發(fā)(同時)執(zhí)行
?:
任務(wù)執(zhí)行完畢后靴患,再執(zhí)行下一個任務(wù)
dispatch_sync
dispatch_sync 和 dispatch_async 用來控制是否要開啟新的線程要出。
隊列的類型鸳君,決定了任務(wù)的執(zhí)行方法(并發(fā)、串行)
1患蹂、并發(fā)隊列
2或颊、串行隊列
3、主隊列(也是一個串行隊列)
4传于、各種隊列的執(zhí)行效果:
并發(fā)隊列 | 手動創(chuàng)建的串行隊列 | 主隊列 | |
---|---|---|---|
同步( sync ) |
|
|
|
異步( async ) |
|
|
|
- 使用 sync 函數(shù)往
隊列中添加任務(wù)囱挑,會卡住當(dāng)前的串行隊列(產(chǎn)生死鎖)
5、問題
問題1:
以下代碼是在主線程執(zhí)行的沼溜,會不會產(chǎn)生死鎖平挑?
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"執(zhí)行任務(wù)1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"執(zhí)行任務(wù)2");
});
NSLog(@"執(zhí)行任務(wù)3");
}
答案:會!執(zhí)行順序是1崩潰系草。
分析:
1通熄、隊列的特點:排隊,F(xiàn)IFO(Fisrst In First Out找都,先進(jìn)先出)
2唇辨、dispatch_sync 同步隊列特點:立馬在當(dāng)前線程執(zhí)行任務(wù),執(zhí)行完畢才能繼續(xù)往下執(zhí)行能耻。
因為任務(wù)2是在同步執(zhí)行赏枚,并且在主隊列中亡驰。而viewDidLoad也是在主隊列中。
所以主隊列中的任務(wù)2會等viewDidLoad這個任務(wù)先執(zhí)行完再執(zhí)行饿幅,而viewDidLoad這個任務(wù)需要執(zhí)行完任務(wù)3才算執(zhí)行完凡辱。所以就出現(xiàn)了死鎖。
問題2:
以下代碼是在主線程執(zhí)行的栗恩,會不會產(chǎn)生死鎖透乾?(和第1題的區(qū)別只是把sync改為了async)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"執(zhí)行任務(wù)1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"執(zhí)行任務(wù)2");
});
NSLog(@"執(zhí)行任務(wù)3");
}
答案:不會!執(zhí)行順序是132摄凡。
分析:異步雖然因為是主隊列不會開啟新線程续徽,但因為是異步的蚓曼,所以不是必須馬上取出任務(wù)2執(zhí)行再執(zhí)行后面的任務(wù)3亲澡,所以可以最后再取出任務(wù)2執(zhí)行。
問題3:
以下代碼是在主線程執(zhí)行的纫版,會不會產(chǎn)生死鎖床绪?
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"執(zhí)行任務(wù)1");
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // Block 0
NSLog(@"執(zhí)行任務(wù)2");
dispatch_sync(queue, ^{ // Block 1
NSLog(@"執(zhí)行任務(wù)3");
});
NSLog(@"執(zhí)行任務(wù)4");
});
NSLog(@"執(zhí)行任務(wù)5");
}
答案:會!執(zhí)行順序是152崩潰其弊。
分析:dispatch_sync
代碼中注釋標(biāo)明了 和
。
要想執(zhí)行完梭伐,必須執(zhí)行完
(因為是sync)才能執(zhí)行后面的任務(wù)4痹雅,才算執(zhí)行完
。但要想執(zhí)行
糊识,又必須先執(zhí)行隊列里先進(jìn)入的
(FIFO)绩社。
和
互相等待,所以造成了死鎖赂苗。
問題4:
以下代碼是在主線程執(zhí)行的愉耙,會不會產(chǎn)生死鎖?
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"執(zhí)行任務(wù)1");
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("myqueue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{ // Block 0
NSLog(@"執(zhí)行任務(wù)2");
dispatch_sync(queue2, ^{ // Block 1
NSLog(@"執(zhí)行任務(wù)3");
});
NSLog(@"執(zhí)行任務(wù)4");
});
NSLog(@"執(zhí)行任務(wù)5");
}
答案:不會拌滋!執(zhí)行順序是15234朴沿。
分析:因為 和
所處兩個不同隊列。就算這道題兩個都是串行隊列败砂,也不會產(chǎn)生死鎖赌渣。
問題5:
以下代碼是在主線程執(zhí)行的,會不會產(chǎn)生死鎖昌犹?
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"執(zhí)行任務(wù)1");
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{ // Block 0
NSLog(@"執(zhí)行任務(wù)2");
dispatch_sync(queue, ^{ // Block 1
NSLog(@"執(zhí)行任務(wù)3");
});
NSLog(@"執(zhí)行任務(wù)4");
});
NSLog(@"執(zhí)行任務(wù)5");
}
答案:不會锡垄!執(zhí)行順序是15234。
分析:因為 和
處在并發(fā)隊列祭隔,并發(fā)隊列不會阻塞货岭。
- 總結(jié):
使用 sync 函數(shù)往隊列中添加任務(wù)路操,會卡住當(dāng)前的串行隊列(產(chǎn)生死鎖)
死鎖 的產(chǎn)生兩個條件:
1、是 sync 同步的千贯;
2屯仗、往隊列添加任務(wù);
6搔谴、多線程的安全隱患
資源共享
? 1 塊資源可能會被多個線程共享魁袜,也就是
? 比如多個線程訪問同一個對象、同一個變量敦第、同一個文件峰弹。當(dāng)多個線程訪問同一塊資源時,很容易引發(fā)
問題
7芜果、面試題
面試題1:
請問下面代碼的打印結(jié)果是什么鞠呈?
打印結(jié)果是:1、3
原因:
1右钾、
performSelector:withObject:afterDelay:
的本質(zhì)是往 Runloop 中添加定時器2蚁吝、子線程默認(rèn)沒有啟動 Runloop。
如果換成
performSelector:withObject
則打印結(jié)果為 132窘茁,這個不帶 Delay 的方法不是 runloop 的,相當(dāng)于直接調(diào)用脆烟。
runloop 的代碼是沒有開源的山林,所以看不到 afterDelay 的實現(xiàn),如果想了解邢羔,可以看 GNUstep驼抹。
- GNUstep
GNUstep 是 GNU 計劃的項目之一,它將 Cocoa 的 OC 庫重新開源實現(xiàn)了一遍张抄。
源碼地址:http://www.gnustep.org/resources/downloads.php
雖然 GNUstep 不是蘋果官方源碼砂蔽,但還是具有一定的參考價值。
面試題2:
請問下面代碼的打印結(jié)果是什么署惯?
打印結(jié)果是:1 崩潰
分析:在執(zhí)行 performSelector 的時候左驾,目標(biāo)線程已經(jīng)退出了,所以崩潰了极谊」钣遥可以在block中開啟runloop:
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
這樣就會打印 1 和 2 了。因為里面啟動了一個 runloop轻猖,執(zhí)行完打印 1 以后線程并沒有死帆吻,而是 runloop 休眠了,之后 performSelector 喚醒runloop去執(zhí)行test咙边。
以上的總結(jié)參考了并部分摘抄了以下文章猜煮,非常感謝以下作者的分享4卧薄:
小馬哥-李明杰的《多線程》課程
轉(zhuǎn)載請備注原文出處,不得用于商業(yè)傳播——凡幾多