iOS中多線程
首先看一道面試題
iOS中多線程有哪些實現(xiàn)方案?
技術(shù)方案 | 簡介 | 語言 | 線程聲明周期 | 使用頻率 |
---|---|---|---|---|
pthread | 1. 一套通用的多線程API 2. 跨平臺/可移植 3. 使用難度大 | C | 程序員管理 | 幾乎不用 |
NSThread | 1.面向?qū)ο?2.簡單易用直接操作線程對象 | OC | 程序員管理 | 偶爾使用 |
GCD | 1.旨在替代NSThread等線程技術(shù) 2.充分利用設(shè)備的多核 | C | 自動管理 | 經(jīng)常使用 |
NSOperation | 1.基于GCD 2.比GCD多了一些更實用的函數(shù) | OC | 自動管理 | 經(jīng)常使用 |
iOS中,多線程一般有三種方案GCD
涯保、NSOperation
和NSThread
。
這一篇文章周伦,會學習iOS中關(guān)于多線程相關(guān)的問題夕春,以及面試中的問題。
一专挪、GCD
GCD相關(guān)問題一般分為三個方面:首先及志,同步/異步
和串行/并發(fā)
問題;其次狈蚤,dispatch_barrier_async
異步柵欄調(diào)用困肩,解決多讀單寫問題;最后脆侮,dispatch_group
使用和理解锌畸。
GCD中有2中用來執(zhí)行任務的函數(shù):同步和異步;同時還有兩種類型的隊列:并發(fā)和串行隊列靖避。并發(fā)隊列讓多個任務并發(fā)執(zhí)行潭枣,自動開啟多個線程同時執(zhí)行任務。并發(fā)功能只在異步函數(shù)下才生效幻捏。
并發(fā)隊列 | 手動創(chuàng)建的串行隊列 | 主隊列 | |
---|---|---|---|
同步 | 沒有開辟新線程 串行執(zhí)行 | 沒有開辟新線程 串行執(zhí)行 | 沒有開辟新線 串行執(zhí)行 |
異步 | 有開辟新線程 并發(fā)執(zhí)行 | 有開辟新線程 串行執(zhí)行 | 沒有開辟新線程 串行執(zhí)行 |
注意:使用同步函數(shù)往當前串行隊列中添加任務盆犁,會卡主當前的串行隊列,產(chǎn)生死鎖篡九。
1.1 同步/異步
和串行/并發(fā)
存在四種組合方案:
// 同步 + 串行
dispatch_sync(serial_queue, ^{
//任務
});
// 異步 + 串行
dispatch_async(serial_queue, ^{
//任務
});
// 同步 + 并發(fā)
dispatch_sync(concurrent_queue, ^{
//任務
});
// 異步 + 并發(fā)
dispatch_async(concurrent_queue, ^{
//任務
});
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í)行。因此導致隊列循環(huán)等待的問題帅涂。
上面的問題理解议薪,在來看一個問題。
- (void)viewDidLoad {
dispatch_sync(serial_queue, ^{
[self doSomething];
});
}
上面這道題媳友,有什么問題斯议?
沒有問題。這里是將
block
添加到單獨的串行隊列庆锦。viewDidLoad()
在主隊列中在主線程中執(zhí)行捅位,在其執(zhí)行過程中調(diào)用block
添加到串行隊列中,在主線程中執(zhí)行搂抒。同步方式提交任務艇搀,無論在串行隊列還是并發(fā)隊列都會在當前線程中執(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求晶。同步方式提交任務焰雕,無論在串行隊列還是并發(fā)隊列都會在當前線程中執(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爵赵。提交異步任務到并發(fā)隊列,任務調(diào)用了
performSelector:withObject:afterDelay:
吝秕。由于GCD提交的任務是在某個子線程中執(zhí)行,子線程沒有RunLoop空幻。由于performSelector:withObject:afterDelay:
需要RunLoop才可生效烁峭,所以方法不執(zhí)行。這個問題秕铛,考察performSelector:withObject:afterDelay:
內(nèi)部實現(xiàn)约郁。
任務和隊列示例代碼:任務和隊列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ā)隊列的處理又開始并行執(zhí)行诱鞠。
三、dispatch_group_async()
面試題:
如何用GCD 實現(xiàn):A这敬、B航夺、C三個任務并發(fā),完成后執(zhí)行任務D崔涂?
實現(xiàn)追加到并發(fā)隊列中的多個任務全部結(jié)束后再執(zhí)行想執(zhí)行的任務阳掐。無論向什么樣的隊列中追加處理,使用Dispatch Group都可監(jiān)視這些處理執(zhí)行的結(jié)束冷蚂。一旦檢測到所有處理執(zhí)行結(jié)束缭保,該Dispatch Group與隊列相同。
dispatch_group_async()
同dispatch_async()函數(shù)相同蝙茶,都追加Block到指定的Dispatch Queue中艺骂。當組中所有任務都執(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(@"任務1 - %@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務2 - %@", [NSThread currentThread]);
}
});
//--------------示例1-------------------
// 寫法一钳恕, 等上面的任務執(zhí)行完成后,才會在主隊列中執(zhí)行任務3
// dispatch_group_notify(group, queue, ^{
// dispatch_async(dispatch_get_main_queue(), ^{
// for (int i = 0; i < 5; i++) {
// NSLog(@"任務3 - %@", [NSThread currentThread]);
// }
// });
// });
//寫法二:直接在主隊列中執(zhí)行
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// for (int i = 0; i < 5; i++) {
// NSLog(@"任務3 - %@", [NSThread currentThread]);
// }
// });
//--------------示例2-------------------
// 如果有多個notify會怎么執(zhí)行呢蹄衷?
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務3 - %@", [NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務4 - %@", [NSThread currentThread]);
}
});
// 任務3和任務4是交替執(zhí)行的
}
另外忧额,也可以使用dispatch_group_wait
,如下:
// 監(jiān)控任務是否完成愧口,當完成時會返回0睦番,不完成一直等待。
long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
if (result == 0) {
NSLog(@"全部任務執(zhí)行完成");
}
線程組示例代碼:線程組API使用Demo
四调卑、NSOperation
需要和NSOperationQueue配合使用來實現(xiàn)多線程抡砂。優(yōu)勢和特點:添加任務依賴、任務執(zhí)行狀態(tài)控制恬涧、控制最大并發(fā)量注益。
任務狀態(tài)的控制
isReady
isExecuting
isFinished
isCanceled
如果重寫main方法,底層控制變更任務執(zhí)行完成狀態(tài)溯捆,以及任務退出丑搔。
如果重寫start方法厦瓢,自行控制任務狀態(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)聽所有任務結(jié)束。
dispatch_group_create() //創(chuàng)建一個隊列組梁呈,需要手動release
dispatch_group_async(組曹铃,隊列,block)
dispatch_group_notify(組捧杉,隊列陕见,block) // 所有任務都結(jié)束后調(diào)用
也可以使用dispatch_group_wait()
dispatch_barrier_async() 一般用于數(shù)據(jù)庫操作和文件讀寫。
同步任務
dispatch_sync(隊列味抖,block)
異步任務
dispatch_async(隊列评甜,block)
指定執(zhí)行次數(shù)
dispatch_apply(次數(shù),隊列仔涩,block)
掛起
dispatch_suspend(隊列)
恢復
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佩研,等待。當計數(shù)值大于1或者等于1時霞揉,減去1而不等待旬薯。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// 發(fā)送計數(shù)值加一信號
dispatch_semaphore_signal(semaphore);
// 等待計數(shù)值變化,第二個參數(shù)是等待時間适秩,這里是永久等待绊序。
// 當計數(shù)值大于0時硕舆,返回0。等于0不會返回任何值骤公。
long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (result == 0) {
NSLog(@"執(zhí)行排他性操作");
}
dispatch_once函數(shù)是保證在應用程序執(zhí)行中只執(zhí)行一次的API抚官。
dispatch I/O 分割讀取,提高讀取效率阶捆。
小結(jié)
通過幾道面試題理解iOS中幾種多線程解決方案凌节。筆者認為該課程可以用作知識梳理,將解讀的內(nèi)容不是很全面洒试,其他的內(nèi)容可以看《小馬哥底層原理視頻》刊咳。