(由于合在一起感覺一篇太長(zhǎng)翻著累)
iOS多線程目前總結(jié)了四篇
- iOS基礎(chǔ)深入補(bǔ)完計(jì)劃--多線程(面試題)匯總
- iOS基礎(chǔ)深入補(bǔ)完計(jì)劃--NSThread
- iOS基礎(chǔ)深入補(bǔ)完計(jì)劃--GCD
- iOS基礎(chǔ)深入補(bǔ)完計(jì)劃--NSOperation
歡迎移步O(∩_∩)O
說(shuō)是面試題匯總锡移、但實(shí)際上是我個(gè)人如果聊到多線程可能會(huì)話趕話聊到的問題。如果有新的問題漆际、歡迎評(píng)論留言罩抗。
目錄
-
實(shí)際問題
- 子線程同時(shí)執(zhí)行ABC三個(gè)同步任務(wù)、全部執(zhí)行完成再在子線程執(zhí)行三個(gè)同步任務(wù)EDF灿椅。
- 上一題中的ABC三個(gè)任務(wù)改成異步任務(wù)(如AFN網(wǎng)絡(luò)請(qǐng)求)、全部回調(diào)成功后進(jìn)行數(shù)據(jù)整合钞支。
- 實(shí)現(xiàn)本地大體量數(shù)組內(nèi)容的實(shí)時(shí)輸入搜索(如通訊錄搜索好友名稱/ID)茫蛹。
-
問題匯總
- 并行和并發(fā)的區(qū)別?
- 串行/并行烁挟、同步異步的區(qū)別?(附帶如何判斷GCD的執(zhí)行順序婴洼、是否開辟線程)
-
NSOperation
與GCD
的關(guān)系 - 默認(rèn)最大并發(fā)
- 線程取消
-
[thread cancel]
可以關(guān)閉線程? -
performSelector
開頭那么多方法為什么分散在不同的文件里撼嗓? -
NSBlockOperation
和NSInvocationOperation
有什么關(guān)系和區(qū)別柬采。 -
NSInvocationOperation
如何解決參數(shù)受限的問題 -
NSOperation
可以像GCD
一樣設(shè)置串行并行么? -
NSOperation
隊(duì)列內(nèi)操作執(zhí)行的時(shí)間點(diǎn) -
NSOperation
設(shè)置優(yōu)先級(jí)是否可以直接決定操作的執(zhí)行順序? -
NSBlockOperation
用addExecutionBlock
追加的操作且警、是否為串行執(zhí)行粉捻。如果不是、為什么要這么設(shè)計(jì)斑芜? - 主隊(duì)列(
[NSOperationQueue mainQueue]
)可以不可以修改最大并發(fā)數(shù)肩刃?主隊(duì)列下添加的操作、都會(huì)在主線程執(zhí)行么? - GCD的并行隊(duì)列一定會(huì)開辟新的線程盈包?
-
dispatch_once
如何實(shí)現(xiàn)一次性代碼沸呐? -
NSOperation
的添加進(jìn)隊(duì)列后可不可以追加依賴?GCD任務(wù)組
添加監(jiān)聽后可不可以追加任務(wù)?
不同線程對(duì)比
實(shí)際問題
這個(gè)不太可能讓人真的手寫代碼或者上機(jī)、根據(jù)回答的點(diǎn)大概可以揣測(cè)一些水平吧呢燥?
-
子線程同時(shí)執(zhí)行ABC三個(gè)同步任務(wù)崭添、全部執(zhí)行完成再在子線程執(zhí)行三個(gè)同步任務(wù)EDF。
說(shuō)隊(duì)列組/依賴基本可以確定了解GCD/NSOpertion叛氨。但是比較麻煩呼渣、用線程?hào)艡?code>dispatch_barrier的話會(huì)更簡(jiǎn)便一些。
-
上一題中的ABC三個(gè)任務(wù)改成異步任務(wù)(如AFN網(wǎng)絡(luò)請(qǐng)求)力试、全部回調(diào)成功后進(jìn)行數(shù)據(jù)整合徙邻。
如果只說(shuō)隊(duì)列/任務(wù)組肯定不行。因?yàn)榫W(wǎng)絡(luò)請(qǐng)求本身是異步的畸裳、任務(wù)會(huì)立即完成缰犁、但數(shù)據(jù)還沒有回來(lái)。這樣就有兩種方式
1怖糊、把異步的網(wǎng)絡(luò)請(qǐng)求轉(zhuǎn)化為同步帅容、以捕獲正確的完成時(shí)機(jī)。
具體操作需要使用信號(hào)量
伍伤。
2并徘、是使用dispatch_group_enter
以及dispatch_group_leave
的搭配。
這個(gè)要感謝評(píng)論區(qū)《9b298c9c5162》的提示扰魂。
-
實(shí)現(xiàn)本地大體量數(shù)組內(nèi)容的實(shí)時(shí)輸入搜索(如通訊錄搜索好友名稱/ID)
1麦乞、每次輸入字符的時(shí)候。如何進(jìn)行數(shù)據(jù)遍歷(如果用的For劝评、那么為什么不用GCD的快速迭代姐直?----因?yàn)殚_辟線程以及線程同步需要些許耗時(shí)、對(duì)于非耗時(shí)操作蒋畜、for的性能會(huì)更好一些)声畏。
2、每次輸入字符的時(shí)候姻成。如何廢棄之前的搜索任務(wù)插龄、以免重復(fù)插入(NSOperationQueue之類)。
3科展、高頻次使用searchArray數(shù)組時(shí)的安全性均牢。(鎖)
問題匯總
-
并行和并發(fā)的區(qū)別?
- 并發(fā)是指兩個(gè)或多個(gè)事件在同一時(shí)間間隔內(nèi)發(fā)生才睹。
例如單CPU的處理多線程膨处。 - 并行是指兩個(gè)或者多個(gè)事件在同一時(shí)刻發(fā)生
例如多CPU的處理多線程见秤。
- 并發(fā)是指兩個(gè)或多個(gè)事件在同一時(shí)間間隔內(nèi)發(fā)生才睹。
-
串行/并行、同步異步的區(qū)別?(附帶如何判斷GCD的執(zhí)行順序真椿、是否開辟線程)
(同步/異步)任務(wù)
決定代碼塊是否會(huì)阻塞當(dāng)前線程鹃答、并且插入指定隊(duì)列的末尾執(zhí)行。(串行/并行/主)隊(duì)列
異步任務(wù)下突硝、(串行/并行/主)隊(duì)列決定將由哪條隊(duì)列的線程執(zhí)行代碼塊测摔。
串行隊(duì)列/主隊(duì)列維護(hù)一條線程、并行隊(duì)列維護(hù)多條線程解恰。同步任務(wù)下锋八、(串行/并行)隊(duì)列使得代碼塊一定是在當(dāng)前線程執(zhí)行(主隊(duì)列則交給主隊(duì)列執(zhí)行)、但要考慮死鎖护盈。
- 死鎖的條件
1. 在一個(gè)串行隊(duì)列維護(hù)的線程內(nèi)挟纱、讓該串行隊(duì)列執(zhí)行同步任務(wù)。
最經(jīng)典的就是腐宋、在主線程內(nèi)讓主線程執(zhí)行同步任務(wù)紊服。
既阻塞了當(dāng)前線程、又想在當(dāng)前線程末尾執(zhí)行胸竞。
這里不管你中間怎么使用欺嗤、只通過線程和隊(duì)列就能判斷。
- 比較奇葩一個(gè)循環(huán)內(nèi)卫枝、連續(xù)的同步任務(wù)串行下出現(xiàn)兩次相同的串行隊(duì)列煎饼。
感覺沒人會(huì)這么寫、除非是神經(jīng)病校赤。
但如果其中修改一個(gè)為并行對(duì)列吆玖、或者異步任務(wù)。就沒有問題马篮。
判斷最終結(jié)果時(shí):先看任務(wù)類型沾乘、然后看隊(duì)列情況。
-
NSOperation
與GCD
的關(guān)系
GCD基于C积蔚、NSOperation基于GCD的封裝。
-
默認(rèn)最大并發(fā)
- NSThread
本身并不會(huì)限制烦周、也不支持限制最大并發(fā)(起碼支持是四位數(shù)以內(nèi)尽爆、如果超過某個(gè)閾值會(huì)error[NSThread start]: Thread creation failed with error 35
)
- NSThread
- (void)thread_bingfa {
for (int a = 0; a < 1000; a ++) {
[self performSelectorInBackground:@selector(aaa:) withObject:[NSNumber numberWithInt:a]];
}
}
- (void)aaa:(NSNumber *)number {
for (int a = 0; a < 100; a++) {
sleep(1);
NSLog(@"%@",number);
}
}
- NSOperation
默認(rèn)的限制大概三位數(shù)以下(我模擬器分配到了63)
self.operationQueue=[[NSOperationQueue alloc]init];
- (void)viewDidLoad {
for (int a = 0; a < 10000; a ++) {
[self bbb:a];
}
}
- (void)bbb:(int)a {
[self.operationQueue addOperationWithBlock:^{
for (int i = 0; i < 100; i ++) {
sleep(10);
NSLog(@"%d",a);
}
}];
}
- GCD
默認(rèn)的最大并發(fā)和NSOperation相同。
畢竟NSOperation基于GCD的OC封裝读慎、倒也說(shuō)得通漱贱。 -
[thread cancel]
可以關(guān)閉線程?
不能夭委、只能把對(duì)應(yīng)線程進(jìn)行cancel標(biāo)記幅狮。詳見下文NSThread相關(guān)的幾個(gè)坑
。
-
performSelector
開頭那么多方法為什么分散在不同的文件里?
分散在NSThread.h
、NSRunLoop.h
崇摄、NSObject.h
擎值。
詳見下文一些NSObject的相關(guān)擴(kuò)展方法(performSelector)
.
-
NSBlockOperation
和NSInvocationOperation
有什么關(guān)系和區(qū)別。- 二者都是NSOperation的子類逐抑、都可以被添加進(jìn)隊(duì)列中(或者自己主動(dòng))執(zhí)行鸠儿。
- NSBlockOperation可以解決NSInvocationOperation傳遞參數(shù)受限的問題。
-
NSInvocationOperation
如何解決參數(shù)受限的問題
這個(gè)問題其實(shí)和解決- (id)performSelector:(SEL)aSelector withObject:(id)object
下方法受限的方式一樣厕氨。
1进每、使用字典。
2命斧、NSInvocationOperation實(shí)際上就是方法簽名NSInvocation
田晚。所以如果使用NSInvocation
進(jìn)行初始化也能解決參數(shù)受限的問題、只是太麻煩了国葬、除非特定情況(目前我接觸到的只有模塊化的Route層)不然不推薦贤徒。
-
NSOperation
可以像GCD一樣設(shè)置串行并行么?
串行并行實(shí)際上是GCD的名詞胃惜。
并行意味著多線程執(zhí)行任務(wù)泞莉、串行意味著單線程執(zhí)行任務(wù)。
任務(wù)在每一個(gè)線程內(nèi)部船殉、其實(shí)都是串行的鲫趁。
NSOperation并沒有串行并行的概念、自然也談不上設(shè)置利虫。
但是我們可以通過通過設(shè)置某個(gè)隊(duì)列(NSOperationQueue)的大并發(fā)數(shù)為1挨厚、讓其中任務(wù)們(NSOperation)自動(dòng)被分配到不同線程中自動(dòng)執(zhí)行、以達(dá)到串行/并行的底層結(jié)果糠惫。
-
NSOperation
隊(duì)列內(nèi)操作執(zhí)行的時(shí)間點(diǎn):- 所有操作在被添加到隊(duì)列中時(shí)疫剃、立即進(jìn)行如下判斷:
- 如果所插入的操作存在依賴關(guān)系、優(yōu)先完成依賴操作硼讽。
-
如果所插入的操作不存在依賴關(guān)系巢价、隊(duì)列并發(fā)數(shù)為1下采用先進(jìn)先出的原則、反之直接開辟新的線程執(zhí)行固阁。
(具體可見下文NSOperation --> 操作的執(zhí)行順序)
- 當(dāng)一個(gè)操作執(zhí)行完成之后壤躲、隊(duì)列會(huì)取出對(duì)其有依賴的所有操作、進(jìn)行下一步判斷:
- 如果該操作沒有其他依賴(準(zhǔn)備就緒备燃、
isReady
屬性)碉克、進(jìn)行下一步判斷 - 所有可以執(zhí)行的操作根據(jù)優(yōu)先級(jí)排序執(zhí)行。
(具體可見下文NSOperation --> 操作的優(yōu)先級(jí))
- 如果該操作沒有其他依賴(準(zhǔn)備就緒备燃、
- 所有操作在被添加到隊(duì)列中時(shí)疫剃、立即進(jìn)行如下判斷:
-
NSOperation
設(shè)置優(yōu)先級(jí)是否可以直接決定操作的執(zhí)行順序?
不能并齐、優(yōu)先級(jí)的判定是建立在依賴操作完成后對(duì)下一步操作的排序下漏麦。
具體可見下文NSOperation --> 操作的優(yōu)先級(jí)
-
NSBlockOperation
用addExecutionBlock
追加的操作客税、是否為串行執(zhí)行。如果不是撕贞、為什么要這么設(shè)計(jì)更耻?
不是、默認(rèn)的操作會(huì)被置于隊(duì)列開辟的首個(gè)線程(主隊(duì)列則為主線程)麻掸、剩余的操作會(huì)開辟新的線程并發(fā)執(zhí)行酥夭。但是有并發(fā)數(shù)限制、由系統(tǒng)分配脊奋。
至于為什么這么設(shè)計(jì)熬北。
NSBlockOperation下所有的操作默認(rèn)情況下也是并行的。由并行通過依賴控制成串行容易诚隙、但由由串行想做出并行的效果則很難讶隐。
比如需要同時(shí)下載三張圖片下載完成之后、將其展示久又。
我可以將三個(gè)下載操作追加進(jìn)一個(gè)blockOperation1巫延、再讓展示操作的BlockOperation2依賴BlockOperation1。
NSOperationQueue *operationQueue=[NSOperationQueue mainQueue];
NSBlockOperation *blockOperation1=[NSBlockOperation blockOperationWithBlock:^{
sleep(1);
NSLog(@"下載任務(wù)--%d",1);
}];
for (int i=2; i<4; ++i) {
[blockOperation1 addExecutionBlock:^{
sleep(1);
NSLog(@"下載任務(wù)--%d",i);
}];
}
NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
sleep(1);
NSLog(@"展示任務(wù)");
}];
[blockOperation2 addDependency:blockOperation1];
[operationQueue addOperation:blockOperation1];
[operationQueue addOperation:blockOperation2];
打印結(jié)果
2018-03-16 16:34:02.388806+0800 test[5620:522718] 下載任務(wù)--2
2018-03-16 16:34:02.388806+0800 test[5620:522602] 下載任務(wù)--1
2018-03-16 16:34:02.388806+0800 test[5620:522717] 下載任務(wù)--3
2018-03-16 16:34:03.390790+0800 test[5620:522602] 展示任務(wù)
如果addExecutionBlock
的操作是串行的地消。那么我只能創(chuàng)建三個(gè)下載操作炉峰、然后將展示操作依賴于以上三個(gè)操作。得不償失脉执。
-
主隊(duì)列(
[NSOperationQueue mainQueue]
)可以不可以修改最大并發(fā)數(shù)疼阔?主隊(duì)列下添加的操作、都會(huì)在主線程執(zhí)行么半夷?- 不能婆廊、主隊(duì)列的最大并發(fā)數(shù)始終為1(自定義隊(duì)列默認(rèn)為-1)、且修改無(wú)效巫橄。
- 默認(rèn)狀況下是的淘邻、但也有例外(追加操作
addExecutionBlock
)。
-
GCD的并行隊(duì)列一定會(huì)開辟新的線程湘换?
不
-
dispatch_once
如何實(shí)現(xiàn)一次性代碼宾舅?
詳情可以查閱GCD-->一次性代碼(單例)
-
NSOperation
的添加進(jìn)隊(duì)列后可不可以追加依賴?GCD任務(wù)組
添加監(jiān)聽后可不可以追加任務(wù)?- NSOperation的依賴必須在添加進(jìn)隊(duì)列(并且執(zhí)行前)之前設(shè)置。(但是我們可以對(duì)某被依賴的操作進(jìn)行追加
addExecutionBlock
以延緩調(diào)用) - GCD任務(wù)組則具備追加任務(wù)的功能彩倚。前提是監(jiān)聽并未被觸發(fā)筹我。
- NSOperation的依賴必須在添加進(jìn)隊(duì)列(并且執(zhí)行前)之前設(shè)置。(但是我們可以對(duì)某被依賴的操作進(jìn)行追加
具體可以詳見相應(yīng)下文NSOperation --> 隊(duì)列插入操作后的執(zhí)行順序
、下文GCD -- > 隊(duì)列組
不同線程對(duì)比
主要說(shuō)GCD和NSOperation署恍、如果NSThread方便實(shí)現(xiàn)的話可能會(huì)提一句崎溃。
線程切換
- NSThread
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self performSelectorInBackground:@selector(fun1) withObject:nil];
}
- (void)fun1 {
//回到主線程
[self performSelectorOnMainThread:@selector(fun2) withObject:nil waitUntilDone:nil];
}
- GCD
創(chuàng)建/獲取一個(gè)并行隊(duì)列添加任務(wù)蜻直、然后返回主隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(queue, ^{
// 執(zhí)行耗時(shí)的異步操作...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主線程盯质,執(zhí)行UI刷新操作
});
});
- NSOperation
創(chuàng)建隊(duì)列添加操作袁串、然后返回主隊(duì)列
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
//切換到子線程
[queue addOperationWithBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//切換回主線程
}];
}];
-
隊(duì)列組/依賴
- GCD
- (void)dispatch_group_test {
dispatch_queue_t queue = dispatch_queue_create("queue_test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"任務(wù)1——準(zhǔn)備休眠3秒");
sleep(3);
NSLog(@"任務(wù)1——完成");
});
NSLog(@"主線程——準(zhǔn)備休眠5秒");
sleep(5);
NSLog(@"主線休眠結(jié)束");
dispatch_group_async(group, queue, ^{
NSLog(@"任務(wù)2——準(zhǔn)備休眠10秒");
sleep(10);
NSLog(@"任務(wù)2——完成");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"任務(wù)組完成");
});
NSLog(@"主線程結(jié)束");
}
- NSOperation
//創(chuàng)建操作隊(duì)列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
//創(chuàng)建最后一個(gè)操作
NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
sleep(1);
NSLog(@"最后的任務(wù)");
}];
for (int i=0; i<5-1; ++i) {
//創(chuàng)建多線程操作
NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
sleep(i);
NSLog(@"第%d個(gè)任務(wù)",i);
}];
//設(shè)置依賴操作為最后一個(gè)操作
[blockOperation addDependency:lastBlockOperation];
[operationQueue addOperation:blockOperation];
}
//將最后一個(gè)操作加入線程隊(duì)列
[operationQueue addOperation:lastBlockOperation];
- GCD
- 適用于多個(gè)任務(wù)同時(shí)執(zhí)行、可以捕獲所有任務(wù)完成的回調(diào)呼巷。
- 任務(wù)添加到隊(duì)列組囱修、且未全部完成時(shí)可以向任務(wù)組中添加任務(wù)。
- NSOperation
- 適用于多個(gè)任務(wù)之間相互依賴等待王悍、最后完成的時(shí)候并沒有回調(diào)破镰。
- 添加到隊(duì)列中(并且已經(jīng)執(zhí)行)的操作不能再新增依賴、但是可以向追加操作压储。
串行隊(duì)列
- GCD
兩種方式獲取/創(chuàng)建
//主隊(duì)列--串行
dispatch_queue_t queue1 = dispatch_get_main_queue();
//自定義串行隊(duì)列
dispatch_queue_t queue2 = dispatch_queue_create("test_queue", DISPATCH_QUEUE_SERIAL);
- NSOperation
兩種方式獲取/創(chuàng)建
//主隊(duì)列
NSOperationQueue * queue1 = [NSOperationQueue mainQueue];
//自定義隊(duì)列 -- 把并發(fā)改為1
NSOperationQueue * queue2 = [[NSOperationQueue alloc]init];
queue2.maxConcurrentOperationCount = 1;
最大并發(fā)
- GCD
通過信號(hào)量進(jìn)行約束鲜漩。詳見GCD-->信號(hào)量
// 創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
// 創(chuàng)建信號(hào)量,并且設(shè)置值為3
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
NSLog(@"%i",i);
sleep(2);
// 每次發(fā)送信號(hào)則semaphore會(huì)+1集惋,
dispatch_semaphore_signal(semaphore);
});
}
- NSOperation
直接設(shè)置
NSOperationQueue * queue2 = [[NSOperationQueue alloc]init];
queue2.maxConcurrentOperationCount = 3;
最后
本文主要是自己的學(xué)習(xí)與總結(jié)孕似。如果文內(nèi)存在紕漏、萬(wàn)望留言斧正刮刑。如果不吝賜教小弟更加感謝喉祭。