iOS開發(fā)中經(jīng)常要使用到多線程,在面試的時(shí)候也是經(jīng)常問到夺衍,比較常見的面試題有下面這些:
- iOS的多線程方案有哪幾種金蜀?你更傾向于哪一種刷后?
- GCD的隊(duì)列類型。
- 說一下OperationQueue和GCD的區(qū)別渊抄,以及各自的優(yōu)勢(shì)丧裁。
- 線程安全的處理手段有哪些?
- OC你了解的鎖有哪些煎娇?在此基礎(chǔ)上進(jìn)行二次提問“
1.自旋和互斥對(duì)比
2.使用以上鎖需要注意哪些二庵?
3.用C/OC/C++贪染,任選其一催享,實(shí)現(xiàn)自旋或互斥。
下面就帶著這些問題痰憎,來總結(jié)一下多線程的相關(guān)問題攀涵。
iOS中的常見多線程方案
技術(shù)方案 | 簡(jiǎn)介 | 語言 | 線程聲明周期 | 使用頻率 |
---|---|---|---|---|
pthread | 一套通用的多線程API,適用于Unix\Linux\windows等系統(tǒng)以故,跨平臺(tái)怒详,可移植,使用難度大 | C | 程序員管理 | 幾乎不用 |
NSThread | 使用更加面向?qū)ο罄ニ福?jiǎn)單易用善玫,可直接操作線程對(duì)象 | OC | 程序員管理 | 偶爾使用 |
GCD | 旨在替代NSThread等線程技術(shù),充分利用設(shè)備的多核 | C | 自動(dòng)管理 | 經(jīng)常使用 |
NSOperation | 基于GCD茅郎,比GCD多了一些更簡(jiǎn)單實(shí)用的功能系冗,使用更加面向?qū)ο?/td> | OC | 自動(dòng)管理 | 經(jīng)常使用 |
GCD基礎(chǔ)回顧
GCD中有兩個(gè)用來執(zhí)行任務(wù)的函數(shù):
- 用同步的方式執(zhí)行任務(wù)
即在當(dāng)前線程中去做事情
dispatch_sync(dispatch_queue_t _Nonnull queue, ^{})
- 用異步的方式執(zhí)行任務(wù)
即另外開線程去做事情
dispatch_async(dispatch_queue_t _Nonnull queue, ^{})
- 并發(fā)隊(duì)列:
可以讓多個(gè)任務(wù)同時(shí)執(zhí)行(自動(dòng)開啟多個(gè)線程同時(shí)執(zhí)行任務(wù))
并發(fā)功能只有在異步函數(shù)下才有效 - 串行隊(duì)列:
讓任務(wù)一個(gè)接著一個(gè)的執(zhí)行(只會(huì)開啟一條線程,一個(gè)任務(wù)執(zhí)行完畢后惯豆,再執(zhí)行下一個(gè)任務(wù))
同步奔害,異步,串行芯杀,并行
- 同步和異步的主要影響:能不能開啟新的線程
同步:在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步:在新的線程中執(zhí)行任務(wù)却特。具備開啟新線程的能力 - 并發(fā)和和串行的主要影響:任務(wù)的執(zhí)行方法
并發(fā):多個(gè)任務(wù)同時(shí)執(zhí)行(會(huì)開啟多條線程)
串行:一個(gè)任務(wù)執(zhí)行完畢后筛圆,再執(zhí)行下一個(gè)任務(wù)(只會(huì)開啟一個(gè)線程)
下面是各種隊(duì)列的執(zhí)行效果:
并發(fā)隊(duì)列 | 手動(dòng)創(chuàng)建的串行隊(duì)列 | 主隊(duì)列 | |
---|---|---|---|
同步(sync) | 沒有開啟新線程,串行執(zhí)行任務(wù) | 沒有開啟新線程闽晦,串行執(zhí)行任務(wù) | 沒有開啟新線程粉寞,串行執(zhí)行任務(wù) |
異步(async) | 有開啟新線程,并發(fā)執(zhí)行任務(wù) | 有開啟新線程捅儒,串行執(zhí)行任務(wù) | 沒有開啟新線程振亮,串行執(zhí)行任務(wù) |
GCD中死鎖的問題
面試題一:以下代碼在主線程中執(zhí)行,是否會(huì)產(chǎn)生死鎖麸祷?
- (void)viewDidLoad {
[super viewDidLoad];
//問題:以下代碼在主線程中執(zhí)行褒搔,會(huì)不會(huì)產(chǎn)生死鎖?
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"執(zhí)行任務(wù)2");
});
NSLog(@"執(zhí)行任務(wù)3");
}
我們運(yùn)行代碼走孽,發(fā)現(xiàn)產(chǎn)生了崩潰琳状,說明產(chǎn)生了死鎖。下面來分析一下為什么會(huì)產(chǎn)生死鎖:
我們知道困食,dispatch_sync()
是同步執(zhí)行翎承,不會(huì)開辟新線程,并且要dispatch_sync()
的block執(zhí)行完了才會(huì)繼續(xù)往下執(zhí)行叨咖,所以任務(wù)2是加入到了主隊(duì)列中芒澜。主隊(duì)列是串行隊(duì)列,南吮,所以會(huì)串行執(zhí)行加入其中的任務(wù)誊酌,等一個(gè)任務(wù)執(zhí)行完了再執(zhí)行另外一個(gè),在任務(wù)2加入主隊(duì)列之前涂邀,viewDidLoad這個(gè)大任務(wù)已經(jīng)加入了主隊(duì)列箱锐,所以任務(wù)2要等ViewDIdLoad執(zhí)行完,才會(huì)執(zhí)行任務(wù)2浩聋,也就是等到任務(wù)3執(zhí)行完臊恋,才會(huì)執(zhí)行任務(wù)2,但是由于任務(wù)3在任務(wù)2后面坊夫,所以要等到任務(wù)2執(zhí)行完了撤卢,才執(zhí)行任務(wù)3。這樣就造成了任務(wù)2等任務(wù)3拷邢,任務(wù)3等任務(wù)2屎慢,造成死鎖。
面試題二:以下代碼是否會(huì)發(fā)生死鎖:
- (void)viewDidLoad {
[super viewDidLoad];
//問題:以下代碼在主線程中執(zhí)行环肘,會(huì)不會(huì)產(chǎn)生死鎖集灌?
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"執(zhí)行任務(wù)2");
});
//
NSLog(@"執(zhí)行任務(wù)3");
}
運(yùn)行程序,發(fā)現(xiàn)程序并沒有崩潰腌零,并且產(chǎn)生打右娼А:
2018-09-25 21:02:04.155692+0800 TEST[2610:87716] 執(zhí)行任務(wù)3
2018-09-25 21:02:04.168868+0800 TEST[2610:87716] 執(zhí)行任務(wù)2
為什么把同步改成異步,就不死鎖了呢闲询?我們分析一下扭弧,由于隊(duì)列是主隊(duì)列,一定是把任務(wù)2加到主隊(duì)列中呼巴,并且在此之前viewDidLoad的任務(wù)已經(jīng)加入到了主隊(duì)列中泊愧,所以要viewDidLoad執(zhí)行完了才能執(zhí)行任務(wù)2,由于dispatch_async()是異步執(zhí)行屑埋,所以不用等到任務(wù)2執(zhí)行完了再執(zhí)行任務(wù)3痰滋,可以直接執(zhí)行任務(wù)3,任務(wù)3執(zhí)行完了团搞,viewDidLoad也就執(zhí)行完了多艇,也就可以執(zhí)行任務(wù)2了,所以打印的結(jié)果一定是先執(zhí)行任務(wù)3再執(zhí)行任務(wù)2复隆。
面試題3:以下代碼是否會(huì)發(fā)生死鎖:
- (void)viewDidLoad {
[super viewDidLoad];
//問題:以下代碼在主線程中執(zhí)行姆涩,會(huì)不會(huì)產(chǎn)生死鎖骨饿?
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{//1
NSLog(@"執(zhí)行任務(wù)2");
dispatch_sync(queue, ^{
NSLog(@"執(zhí)行任務(wù)3");
});
NSLog(@"執(zhí)行任務(wù)4");
});
NSLog(@"執(zhí)行任務(wù)3");
}
運(yùn)行代碼台腥,發(fā)現(xiàn)在dispatch_sync這里產(chǎn)生了崩潰绒北,打印結(jié)果如下:
2018-09-25 21:15:47.637042+0800 TEST[2816:95982] 執(zhí)行任務(wù)3
2018-09-25 21:15:47.637048+0800 TEST[2816:96030] 執(zhí)行任務(wù)2
下面分析一下為什么會(huì)產(chǎn)生死鎖:
dispatch_async是異步執(zhí)行镇饮,所以先打印了執(zhí)行任務(wù)3
,然后把1這個(gè)block加入了串行隊(duì)列中,這時(shí)串行隊(duì)列中有1這個(gè)block嘶是,然后又向串行隊(duì)列中加入了任務(wù)3聂喇,任務(wù)3需要同步執(zhí)行,所以任務(wù)3執(zhí)行完了才會(huì)執(zhí)行任務(wù)4克饶,由于串行隊(duì)列中1這個(gè)block排在任務(wù)3前面誊辉,所以要1這個(gè)block完成才會(huì)執(zhí)行任務(wù)3,也就是要任務(wù)4執(zhí)行完了才會(huì)執(zhí)行任務(wù)3邀跃,而任務(wù)4又要等到任務(wù)3執(zhí)行完成蛙紫,這樣互相等待,造成死鎖僵驰。
總結(jié)
造成死鎖的條件1是同步唁毒,2是往當(dāng)前的串行隊(duì)列中添加事件枉证。
直接獲取全局隊(duì)列和手動(dòng)創(chuàng)建隊(duì)列的關(guān)系
看下面一段代碼:
dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t queue4 = dispatch_queue_create("muqueue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue5 = dispatch_queue_create("muqueue1", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"%p, %p, %p, %p, %p", queue1, queue2, queue3, queue4, queue5);
打印結(jié)果:
0x1062e2500, 0x1062e2500, 0x1062e2680, 0x600000146bf0, 0x600000146ca0
queue1,queue2毡鉴,queue3是直接獲取的全局隊(duì)列,從打印結(jié)果可以看出憎瘸,如果優(yōu)先級(jí)相同陈瘦,則獲取的是同一個(gè)隊(duì)列,如果優(yōu)先級(jí)不同锅风,則獲取的是不同的隊(duì)列鞍泉。queue4咖驮,queue5是手動(dòng)創(chuàng)建的隊(duì)列,即便它們的identifier相同托修,但是仍然創(chuàng)建了不同的隊(duì)列睦刃。
面試題
面試題一:?jiǎn)栂铝写a的打印結(jié)果:
dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue1, ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
});
- (void)test{
NSLog(@"2");
}
我們先看一下打印結(jié)果:
1
3
很奇怪,2壓根就沒有打印枣宫,也就是壓根就沒有執(zhí)行test方法吃环,這是為什么呢郁轻?
我們把
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
改成
[self performSelector:@selector(test) withObject:nil];
看看打印結(jié)果:
1
2
3
說明這樣是能成功執(zhí)行test函數(shù)的,我們?cè)趓untime中找一下- (id)performSelector:(SEL)aSelector withObject:(id)object;
的源碼:
- (id)performSelector:(SEL)sel withObject:(id)obj {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj);
}
可以看到竭沫,就是簡(jiǎn)單的調(diào)用objc_msgSend()
骑篙。但是在runtime的源碼中卻沒有找到- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
的實(shí)現(xiàn)。
我們?cè)傩薷囊幌麓a谎势,讓其直接在主線程中執(zhí)行:
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
打印結(jié)果:
1
2
3
那么就有理由猜測(cè)NSLog(@"1"); [self performSelector:@selector(test) withObject:nil afterDelay:.0]; NSLog(@"3");
的執(zhí)行和線程有關(guān)脏榆。
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
這句代碼的本質(zhì)是往runloop中去添加一個(gè)NSTimer,由于主線程中有runloop吁断,所以可以正常執(zhí)行坞生,但是在子線程中默認(rèn)是沒有啟動(dòng)runloop的,所以NSTimer也就沒有辦法成功執(zhí)行骂因。
我們可以啟動(dòng)子線程中的runloop試一下:
dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue1, ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
打印結(jié)果:
1
3
2
GNUstep
Foundation框架是不開源的,所以我們想看其中的源碼是看不到的乘盼。GNUstep是GNU計(jì)劃的項(xiàng)目之一绸栅,它將Cocoa的OC庫(kù)重新開源實(shí)現(xiàn)了一遍,雖然它不是蘋果官方的完整實(shí)現(xiàn)粹胯,但是和官方的實(shí)現(xiàn)十分接近风纠,區(qū)別不大,在學(xué)習(xí)的時(shí)候我們可以用來作為參考镐捧。
源碼地址:http://www.gnustep.org/resources/downloads.php
我們下載了GNUstep的代碼后臭增,打開找到Foundation文件夾,在這個(gè)文件夾下找到NSRunLoop.m
這個(gè)文件列牺,在這個(gè)文件中找到- (void) performSelector: (SEL)aSelector withObject: (id)argument afterDelay: (NSTimeInterval)seconds
這個(gè)方法的實(shí)現(xiàn):
- (void) performSelector: (SEL)aSelector
withObject: (id)argument
afterDelay: (NSTimeInterval)seconds
{
NSRunLoop *loop = [NSRunLoop currentRunLoop];
GSTimedPerformer *item;
item = [[GSTimedPerformer alloc] initWithSelector: aSelector
target: self
argument: argument
delay: seconds];
[[loop _timedPerformers] addObject: item];
RELEASE(item);
[loop addTimer: item->timer forMode: NSDefaultRunLoopMode];
}
GCD隊(duì)列組
思考:如何用gcd實(shí)現(xiàn)以下功能:
異步并發(fā)執(zhí)行任務(wù)1瞎领,任務(wù)2
等任務(wù)1,任務(wù)2都執(zhí)行完畢后甥郑,再回到主線程執(zhí)行任務(wù)3
我們可以用dispatch_group_t和dispatch_group_notify來完成這個(gè)功能:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("myQueue", 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]);
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務(wù)3-%@", [NSThread currentThread]);
}
});
如果需要在任務(wù)1和任務(wù)2完成之后再完成任務(wù)3澜搅,任務(wù)4邪锌,可以這樣:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("myQueue", 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]);
}
});
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]);
}
});