
iOS中多線程
首先看一道面試題
iOS中多線程有哪些實現(xiàn)方案?
| 技術(shù)方案 | 簡介 | 語言 | 線程聲明周期 | 使用頻率 |
|---|---|---|---|---|
| pthread | 1. 一套通用的多線程API 2. 跨平臺/可移植 3. 使用難度大 |
C | 程序員管理 | 幾乎不用 |
| NSThread | 1.面向?qū)ο?br> 2.簡單易用直接操作線程對象 | OC | 程序員管理 | 偶爾使用 |
| GCD | 1.旨在替代NSThread等線程技術(shù) 2.充分利用設(shè)備的多核 |
C | 自動管理 | 經(jīng)常使用 |
| NSOperation | 1.基于GCD 2.比GCD多了一些更實用的函數(shù) |
OC | 自動管理 | 經(jīng)常使用 |
iOS中,多線程一般有三種方案GCD、NSOperation和NSThread。
這一篇文章,會學(xué)習(xí)iOS中關(guān)于多線程相關(guān)的問題,以及面試中的問題。
一、GCD
GCD相關(guān)問題一般分為三個方面:首先,同步/異步和串行/并發(fā)問題;其次,dispatch_barrier_async異步柵欄調(diào)用,解決多讀單寫問題;最后,dispatch_group使用和理解。
GCD中有兩種用來執(zhí)行任務(wù)的函數(shù):同步和異步;同時還有兩種類型的隊列:并發(fā)和串行隊列。并發(fā)隊列讓多個任務(wù)并發(fā)執(zhí)行,自動開啟多個線程同時執(zhí)行任務(wù)。并發(fā)功能只在異步函數(shù)下才生效。
| 并發(fā)隊列 | 手動創(chuàng)建的串行隊列 | 主隊列 | |
|---|---|---|---|
| 同步 | 沒有開辟新線程 串行執(zhí)行 |
沒有開辟新線程 串行執(zhí)行 |
沒有開辟新線程 串行執(zhí)行 |
| 異步 | 有開辟新線程 并發(fā)執(zhí)行 |
有開辟新線程 串行執(zhí)行 |
沒有開辟新線程 串行執(zhí)行 |
注意:使用同步函數(shù)往當(dāng)前串行隊列中添加任務(wù),會卡主當(dāng)前的串行隊列,產(chǎn)生死鎖。
1.1 同步/異步 && 串行/并發(fā)
存在四種組合方案:
// 同步 + 串行
dispatch_sync(serial_queue, ^{
//任務(wù)
});
// 異步 + 串行
dispatch_async(serial_queue, ^{
//任務(wù)
});
// 同步 + 并發(fā)
dispatch_sync(concurrent_queue, ^{
//任務(wù)
});
// 異步 + 并發(fā)
dispatch_async(concurrent_queue, ^{
//任務(wù)
});
1.1.1 同步 + 串行
首先看一道面試題
- (void)viewDidLoad {
dispatch_sync(dispatch_get_main_queue(), ^{
[self doSomething];
});
}
上面這道面試題,存在什么問題?
產(chǎn)生死鎖。隊列引起循環(huán)等待。因為,
viewDidLoad()進入主隊列,執(zhí)行過程中會將block添加到主隊列中。viewDidLoad()需要等待block執(zhí)行完成后才能結(jié)束,由于主隊列先進先出的block需要viewDidLoad()執(zhí)行完畢才能執(zhí)行。因此導(dǎo)致隊列循環(huán)等待的問題。
上面的問題理解,在來看一個問題。
- (void)viewDidLoad {
dispatch_sync(serial_queue, ^{
[self doSomething];
});
}
上面這道題,有什么問題?
沒有問題。這里是將
block添加到單獨的串行隊列。viewDidLoad()在主隊列中在主線程中執(zhí)行,在其執(zhí)行過程中調(diào)用block添加到串行隊列中,在主線程中執(zhí)行。同步方式提交任務(wù),無論在串行隊列還是并發(fā)隊列都會在當(dāng)前線程中執(zhí)行
1.1.2 同步 + 并發(fā)
問題
int main(int argc, const char * argv[]) {
@autoreleasepool {
dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"1");
dispatch_sync(global_queue, ^{
NSLog(@"2");
dispatch_sync(global_queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
return 0;
}
上面這段代碼的輸出結(jié)果?
輸出結(jié)果:12345。同步方式提交任務(wù),無論在串行隊列還是并發(fā)隊列都會在當(dāng)前線程中執(zhí)行。
思考,如果換成串行隊列呢?
1.1.3 異步 + 串行
略...
1.1.4 異步 + 并發(fā)
面試題
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(global_queue, ^{
NSLog(@"1");
[self performSelector:@selector(printLog) withObject:nil afterDelay:0];
NSLog(@"3");
});
}
- (void)printLog {
NSLog(@"2");
}
上述代碼執(zhí)行的結(jié)果?
結(jié)果:13。提交異步任務(wù)到并發(fā)隊列,任務(wù)調(diào)用了
performSelector:withObject:afterDelay:。由于GCD提交的任務(wù)是在某個子線程中執(zhí)行,子線程沒有RunLoop。由于performSelector:withObject:afterDelay:需要RunLoop才可生效,所以方法不執(zhí)行。這個問題,考察performSelector:withObject:afterDelay:內(nèi)部實現(xiàn)。
任務(wù)和隊列示例代碼:任務(wù)和隊列Demo包含面試題講解
二、多讀單寫解決方案
-
pthread_rwlock: 讀寫鎖 -
dispatch_barrier_async()異步柵欄調(diào)用
怎么利用GCD實現(xiàn)多讀單寫?或者如何實現(xiàn)多讀單寫?
2.1 什么是多讀單寫?
讀者和讀者,并發(fā)。
讀者和寫者,互斥。
寫者與寫者,互斥。
2.2 解決方法
dispatch_async(global_queue, ^{
NSLog(@"讀取1");
});
dispatch_async(global_queue, ^{
NSLog(@"讀取2");
});
dispatch_barrier_async(global_queue, ^{
NSLog(@"寫入1");
});
dispatch_async(global_queue, ^{
NSLog(@"讀取3");
});
dispatch_async(global_queue, ^{
NSLog(@"讀取4");
});
dispatch_barrier_async函數(shù)會等待追加到并發(fā)隊列上的并行執(zhí)行的處理全部結(jié)束之后,在將指定的處理追加到該并發(fā)隊列中。然后等dispatch_barrier_async函數(shù)追加的處理執(zhí)行完畢后,并發(fā)隊列才恢復(fù)為一般的動作,追加到并發(fā)隊列的處理又開始并行執(zhí)行。
三、dispatch_group_async()
面試題:
如何用GCD 實現(xiàn):A、B、C三個任務(wù)并發(fā),完成后執(zhí)行任務(wù)D?
實現(xiàn)追加到并發(fā)隊列中的多個任務(wù)全部結(jié)束后再執(zhí)行想執(zhí)行的任務(wù)。無論向什么樣的隊列中追加處理,使用Dispatch Group都可監(jiān)視這些處理執(zhí)行的結(jié)束。一旦檢測到所有處理執(zhí)行結(jié)束,該Dispatch Group與隊列相同。
dispatch_group_async() 同dispatch_async()函數(shù)相同,都追加Block到指定的Dispatch Queue中。當(dāng)組中所有任務(wù)都執(zhí)行完成后,dispatch_group_notify()執(zhí)行Block中的內(nèi)容。
示例代碼:
// dispatch_group_notify
- (void)test {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.lqq.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務(wù)1 - %@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務(wù)2 - %@", [NSThread currentThread]);
}
});
//--------------示例1-------------------
// 寫法一, 等上面的任務(wù)執(zhí)行完成后,才會在主隊列中執(zhí)行任務(wù)3
// dispatch_group_notify(group, queue, ^{
// dispatch_async(dispatch_get_main_queue(), ^{
// for (int i = 0; i < 5; i++) {
// NSLog(@"任務(wù)3 - %@", [NSThread currentThread]);
// }
// });
// });
//寫法二:直接在主隊列中執(zhí)行
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// for (int i = 0; i < 5; i++) {
// NSLog(@"任務(wù)3 - %@", [NSThread currentThread]);
// }
// });
//--------------示例2-------------------
// 如果有多個notify會怎么執(zhí)行呢?
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務(wù)3 - %@", [NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務(wù)4 - %@", [NSThread currentThread]);
}
});
// 任務(wù)3和任務(wù)4是交替執(zhí)行的
}
另外,也可以使用dispatch_group_wait,如下:
// 監(jiān)控任務(wù)是否完成,當(dāng)完成時會返回0,不完成一直等待。
long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
if (result == 0) {
NSLog(@"全部任務(wù)執(zhí)行完成");
}
線程組示例代碼:線程組API使用Demo
四、NSOperation
需要和NSOperationQueue配合使用來實現(xiàn)多線程。優(yōu)勢和特點:添加任務(wù)依賴、任務(wù)執(zhí)行狀態(tài)控制、控制最大并發(fā)量。
任務(wù)狀態(tài)的控制
isReady
isExecuting
isFinished
isCanceled
如果重寫main方法,底層控制變更任務(wù)執(zhí)行完成狀態(tài),以及任務(wù)退出。
如果重寫start方法,自行控制任務(wù)狀態(tài)。
面試題
系統(tǒng)是怎樣移除一個isFinished=YES的NSOperation的?答案:KVO
五、NSThread
啟動流程
start() -> 創(chuàng)建pthread -> main() ->[target performSelector:selector] -> exit()
如何實現(xiàn)常駐進程?
通過使用NSThread和RunLoop實現(xiàn)。
附錄
GCD API介紹:
dispatch_queue_create(名稱,類型)
類型為NULL時,為串行隊列;為DISPATCH_QUEUE_CONCUREENT,為并行隊列.。
需要手動release釋放隊列
dispatch_release(隊列)
主隊列,串行
dispatch_get_main_queue()
dispatch_get_global_queue(優(yōu)先級,0)
優(yōu)先級有四種情況:
高,默認,低,后臺
為自己創(chuàng)建的隊列設(shè)置優(yōu)先級
dispatch_set_target_queue(自定義隊列,其他已知優(yōu)先級的隊列)
dispatch_after(時間,隊列,Block)
時間:dispatch_time_t
dispatch_group 監(jiān)聽所有任務(wù)結(jié)束。
dispatch_group_create() //創(chuàng)建一個隊列組,需要手動release
dispatch_group_async(組,隊列,block)
dispatch_group_notify(組,隊列,block) // 所有任務(wù)都結(jié)束后調(diào)用
也可以使用dispatch_group_wait()
dispatch_barrier_async() 一般用于數(shù)據(jù)庫操作和文件讀寫。
同步任務(wù)
dispatch_sync(隊列,block)
異步任務(wù)
dispatch_async(隊列,block)
指定執(zhí)行次數(shù)
dispatch_apply(次數(shù),隊列,block)
掛起
dispatch_suspend(隊列)
恢復(fù)
dispatch_resume(隊列)
信號量
Dispatch Semaphore是持有計數(shù)的信號,該計數(shù)是多線程編程中的計數(shù)類型信號。
dispatch_semaphore_create(技術(shù)值)
dispatch_semaphore_wait(semaphore, 時間)
dispatch_semaphore_signal(semaphore)
dispatch_reliease(semaphore)
// 示例
// 創(chuàng)建一個對象,默認的計數(shù)值為0,等待。當(dāng)計數(shù)值大于1或者等于1時,減去1而不等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// 發(fā)送計數(shù)值加一信號
dispatch_semaphore_signal(semaphore);
// 等待計數(shù)值變化,第二個參數(shù)是等待時間,這里是永久等待。
// 當(dāng)計數(shù)值大于0時,返回0。等于0不會返回任何值。
long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (result == 0) {
NSLog(@"執(zhí)行排他性操作");
}
dispatch_once函數(shù)是保證在應(yīng)用程序執(zhí)行中只執(zhí)行一次的API。
dispatch I/O 分割讀取,提高讀取效率。
小結(jié)
通過幾道面試題理解iOS中幾種多線程解決方案。筆者認為該課程可以用作知識梳理,將解讀的內(nèi)容不是很全面,其他的內(nèi)容可以看《小馬哥底層原理視頻》。
參考文章:
細說 NSOperation