[iOS面試]第6章 多線程相關(guān)面試問題

本文主講多線程相關(guān)面試問題:包括GCD、NSOperation、NSThread、多線程與鎖。

一、GCD

  • 同步/異步 和串行/并發(fā)
  • dispatch_barrier_async 異步柵欄調(diào)用
  • dispatch_group

01 異步函數(shù)+并發(fā)隊(duì)列:開啟多條線程,并發(fā)執(zhí)行任務(wù)
02 異步函數(shù)+串行隊(duì)列:開啟一條線程,串行執(zhí)行任務(wù)
03 同步函數(shù)+并發(fā)隊(duì)列:不開線程,串行執(zhí)行任務(wù)
04 同步函數(shù)+串行隊(duì)列:不開線程,串行執(zhí)行任務(wù)
05 異步函數(shù)+主隊(duì)列:不開線程,在主線程中串行執(zhí)行任務(wù)
06 同步函數(shù)+主隊(duì)列:不開線程,串行執(zhí)行任務(wù)(注意死鎖發(fā)生)
注意同步函數(shù)和異步函數(shù)在執(zhí)行順序上面的差異

1、同步/異步 和 串行/并發(fā)

同步/異步 和 串行/并發(fā)

+ 同步分配任務(wù)到串行隊(duì)列 
dispatch_sync(serial_queue,{//任務(wù)});    
+ 異步分配任務(wù)到串行隊(duì)列
dispatch_async(serial_queue,{//任務(wù)});      
+ 同步分配任務(wù)到并發(fā)隊(duì)列
dispatch_sync(concurrent_queue,{//任務(wù)}); 
+ 異步分配任務(wù)到并發(fā)隊(duì)列
dispatch_async(concurrent_queue,{//任務(wù)});
(1)、同步串行 dispatch_sync(serial_queue , ^{ //任務(wù) });
同步主線程
//同步主線程 死鎖
- (void)viewDidLoad {
     dispatch_sync(dispatch_get_main_queue(), ^{
        [self doSomething];
    });
}
死鎖原因

結(jié)果:造成死鎖 隊(duì)列引起的循環(huán)等待.
在主隊(duì)列中提交了viewDidLoad,然后又提交了block。因此在執(zhí)行viewDidLoad過程中,需要調(diào)用block,block完成之后,viewDidLoad才能繼續(xù)往下執(zhí)行,而block因?yàn)殛?duì)列先進(jìn)先出的性質(zhì)必須要等viewDidLoad執(zhí)行結(jié)果才能調(diào)用,導(dǎo)致相互等待情況,從而死鎖.

同步串行
- (void)viewDidLoad {
     dispatch_sync(serialQueue, ^{
        [self doSomething];
    });
}
同步串行分析

結(jié)果:順序執(zhí)行 都在主線程,不開辟新線程

(2)、同步并發(fā) dispatch_sync(concurrent_queue , ^{ //任務(wù) });
- (void)viewDidLoad {
  NSLog(@"1");
  dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"2");
        dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

答案:12345

  • 順序執(zhí)行 都在主線程 不開辟新線程
    ps:如果2 3 都是添加到同一串行隊(duì)列 就會造成死鎖 23循環(huán)等待

  • 只要同步方式提交任務(wù),無論串行還是并發(fā)都是在當(dāng)前線程執(zhí)行

  • dispatch_sync() 在當(dāng)前主線程執(zhí)行

(3)、異步串行 dispatch_async(serial_queue , ^{ //任務(wù) });

異步主隊(duì)列

- (void)viewDidLoad {
     dispatch_async(dispatch_get_main_queue(), ^{
        [self doSomething];
    });
}
  • 順序執(zhí)行,都在主線程,不開辟新線程
(4)、異步并發(fā) dispatch_async(concurrent_queue , ^{ //任務(wù) });
//騰訊面試題:
- (void)viewDidLoad {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"1");
        [self performSelector:@selector(printLog) withObject:nil afterDelay:0];
        NSLog(@"3");
    });
}
- (void)printLog{ NSLog(@"2"); }
  • 輸出: 13
  • 該題涉及知識點(diǎn)較多:GCD、線程的Runloop、performSeletor內(nèi)部實(shí)現(xiàn)
  • 異步分派到全局隊(duì)列中,GCD底層所分派的線程默認(rèn)是不開啟對應(yīng)runloop的,而performSeletor:即使是延遲0秒,也是需要提交任務(wù)到runloop的邏輯,所以performSeletor方法會失效的
  • performSeletor:方法要想有效執(zhí)行, 必須是方法調(diào)用所屬線程是有runloop的,沒有就會失效

2、dispatch_barrier_async()

多讀單寫方案:dispatch_barrier_async(concurrent_queue , ^{ //寫操作 });

多讀單寫方案
多讀單寫實(shí)現(xiàn)

問題:怎么利用GCD實(shí)現(xiàn)多讀單寫?或者說想要實(shí)現(xiàn)多讀單寫,怎么去實(shí)現(xiàn)?
答:

  • 讀者與讀者并發(fā)(讀操作添加到并發(fā)隊(duì)列同步訪問)
  • 讀者與寫者、寫者與寫者互斥 (寫操作通過dispatch_barrier_async+ 異步柵欄添加到并發(fā)隊(duì)列中)
@interface UserCenter(){
    // 定義一個(gè)并發(fā)隊(duì)列
    dispatch_queue_t concurrent_queue;
    // 用戶數(shù)據(jù)中心, 可能多個(gè)線程需要數(shù)據(jù)訪問
    NSMutableDictionary *userCenterDic;
}
@end

// 多讀單寫模型
@implementation UserCenter
- (id)init{
    self = [super init];
    if (self) {
        // 通過宏定義 DISPATCH_QUEUE_CONCURRENT 創(chuàng)建一個(gè)并發(fā)隊(duì)列
        concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
        // 創(chuàng)建數(shù)據(jù)容器
        userCenterDic = [NSMutableDictionary dictionary];
    }
    return self;
}
- (id)objectForKey:(NSString *)key {
    __block id obj;
    // 同步讀取指定數(shù)據(jù)
    dispatch_sync(concurrent_queue, ^{
        obj = [userCenterDic objectForKey:key];
    });
    return obj;
}
- (void)setObject:(id)obj forKey:(NSString *)key {
    // 異步柵欄調(diào)用設(shè)置數(shù)據(jù)
    dispatch_barrier_async(concurrent_queue, ^{
        [userCenterDic setObject:obj forKey:key];
    });
}
@end

3、dispatch_group_async()

問題:
使用GCD實(shí)現(xiàn):A、B、C三個(gè)任務(wù)并發(fā),完成后執(zhí)行任務(wù)D?
答: 所有異步任務(wù)添加到并發(fā)隊(duì)列中,然后使用dispatch_group_notify函數(shù),來監(jiān)聽前面多個(gè)的任務(wù)是否完成,如果完成, 就會調(diào)用dispatch_group_notify中的block
(參考代碼實(shí)例)

二、NSOperation

需要和NSOperationQueue配合使用來實(shí)現(xiàn)多線程方案。
1、特點(diǎn):添加任務(wù)依賴、任務(wù)執(zhí)行狀態(tài)監(jiān)控、最大并發(fā)數(shù)。
2、任務(wù)執(zhí)行狀態(tài)控制:isReady、isExecuting、isFinished、isCancelled。

3、狀態(tài)監(jiān)控

  • 如果只重寫main方法,底層控制變更任務(wù)執(zhí)行完成狀態(tài),以及任務(wù)退出。
  • 如果重寫start方法,需自行控制任務(wù)狀態(tài)。

4、系統(tǒng)是怎樣移除一個(gè)isFinished=YES的NSOperation的?
答:通過KVO的方式,通知對應(yīng)的NSOprationQueue達(dá)到對NSOperation對象進(jìn)行移除。

三、NSThread

考查面試題:
1> 如何NSThread結(jié)合runloop實(shí)現(xiàn)常駐線程
2> NSThread 的 內(nèi)部實(shí)現(xiàn)機(jī)制, start方法實(shí)現(xiàn)邏輯流程
(結(jié)合 gnustep-base-1.24.9 源碼分析)

NSThread啟動(dòng)流程

問題: NSThread啟動(dòng)流程?
答: start() ——>創(chuàng)建pthread線程——>main()——>[target performSelector:selector]——>exit()

問題: 如何通過runloop和NSThread實(shí)現(xiàn)一個(gè)常駐線程?
從下一章runloop中尋找答案

問題: NSThread執(zhí)行原理是怎樣的?
答: 實(shí)際內(nèi)部創(chuàng)建一個(gè)pthread線程 ,當(dāng)main()函數(shù)或者指定的target 的selector方法執(zhí)行結(jié)束后,系統(tǒng)會為我們進(jìn)行線程的退出管理操作. 如果需要維護(hù)一個(gè)常駐線程,需要NSThread所對應(yīng)的selector方法中維護(hù)runloop事件循環(huán)。

四、多線程與鎖

iOS中有哪些鎖?

  • @synchronized
  • atomic
  • OSSpinLock
  • NSRecursiveLock
  • NSLock
  • dispatch_semaphore_t

1、 @synchronized的使用場景

一般在創(chuàng)建單例的時(shí)候使用,保證在多線程環(huán)境下創(chuàng)建的對象是唯一的

2、atomic

  • 修飾屬性的關(guān)鍵字
  • 對被修飾的對象進(jìn)行原子操作(不負(fù)責(zé)使用,只負(fù)責(zé)賦值)
@property(atomic)NSMutableArray *array;
self.array = [NSMutableArray array];  //array賦值操作,能保證線程安全
[self.array addObject:obj];  // array使用, 不能保證線程安全,需要額外做線程安全保護(hù)

3、OSSpinLock自旋鎖

  • 循環(huán)等待詢問,不釋放當(dāng)前資源
  • 用于輕量級數(shù)據(jù)訪問,簡單的int值+1/-1操作

沒有具體用過,但是可以通過分析runtime源碼來學(xué)習(xí)系統(tǒng)關(guān)于OSSpinLock自旋鎖的使用情況.

4、 NSLock

  • 一般用于解決細(xì)粒度的線程同步問題, 來保證各個(gè)線程互斥,進(jìn)入自己的臨界區(qū).
- (void)methodA {
  [lock lock];
  [self methodB];
  [lock unlock];
}
- (void)methodB {
  [lock lock];
  //操作邏輯
  [lock unlock];
}
  • 該寫法重入的原因 會導(dǎo)致死鎖!
  • 解決: 通過NSRecursiveLock遞歸所可以解決

5、NSRecursiveLock 遞歸鎖

  • NSRecursiveLock 遞歸鎖特性: 可以重入
- (void)methodA {
  [recursiveLock lock];
  [self methodB];
  [recursiveLock unlock];
}
- (void)methodB {
  [recursiveLock lock];
  //操作邏輯
  [recursiveLock unlock];
}

6、dispatch_semaphore_t 信號量

  • dispatch_semaphore_t 信號量 也是用來實(shí)現(xiàn)線程同步, 包括對共享資源互斥訪問的信號量機(jī)制,類似于計(jì)算機(jī)專業(yè)的記錄型信號量

  • 創(chuàng)建信號量 dispatch_semaphore_create(1)

//dispatch_semaphore_create內(nèi)部實(shí)現(xiàn) 實(shí)例化一個(gè)結(jié)構(gòu)體
struct semaphore{
  int value;  // 信號量的值
  List<thread>; // 線程的進(jìn)程控制表pcd 或者一些其他線程的一個(gè)唯一標(biāo)識所維護(hù)的一個(gè)線程列表
}
  • dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER)
    信號量-1,阻塞是一個(gè)主動(dòng)行為
//dispatch_semaphore_wait() 實(shí)現(xiàn)邏輯
{
  S.value = S.value - 1;
  if S.value < 0 then Block(S.List); //阻塞是一個(gè)主動(dòng)行為
}
  • dispatch_semaphore_signal(semaphore) 信號量+1,喚醒是一個(gè)被動(dòng)行為
//dispatch_semaphore_signal()實(shí)現(xiàn)邏輯
{
  S.value = S.value + 1;
  if S.value <= 0 then wakeup(S.List); //喚醒是一個(gè)被動(dòng)行為
}

五 多線程相關(guān)面試問題

1、怎樣用GCD實(shí)現(xiàn)多讀單寫?
答: dispatch_barrier_async()的使用.

  • 讀者與讀者并發(fā)(讀操作添加到并發(fā)隊(duì)列同步訪問)
  • 讀者與寫者、寫者與寫者互斥 (寫操作通過dispatch_barrier_async+ 異步柵欄添加到并發(fā)隊(duì)列中)

2、iOS系統(tǒng)為我們提供的幾種多線程技術(shù)各自的特點(diǎn)是怎么樣的?
答案:GCD、NSOperation、NSThread。

  • GCD 用來實(shí)現(xiàn)簡單的線程同步,包括子線程的分派,包括實(shí)現(xiàn)多讀單寫場景的解決
  • NSOperation及NSOperationQueue 比如AFNetworking、SDWebImage都會涉及到NSOpration,由于它的特點(diǎn)是方便我們對任務(wù)的狀態(tài)進(jìn)行控制,包括可以控制添加依賴、移除依賴
  • NSThread 一般用它來實(shí)現(xiàn)一個(gè)常駐線程

3、NSOperation對象在Finished之后是咋樣從queue當(dāng)中移除掉的?
答: NSOperation對象在Finished之后, 通過KVO的方式,通知對應(yīng)的NSOprationQueue達(dá)到對NSOperation對象進(jìn)行移除。

4、你都用過哪些鎖?結(jié)合實(shí)際談?wù)勀闶窃鯓邮褂玫模?br> 答:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容