最新整理:iOS面試題-常問多線程問題(六)

前言:

最近把 iOS 面試中可能會遇到的問題整理了一番, 題目大部分是網上收錄的, 方便自己鞏固復習, 也分享給大家; 希望對大家有所幫助!

  • 對于答案,不一定都合適,歡迎大家積極討論;整理不易,如果您覺得還不錯,麻煩在文末 “點個贊” ,或者留下您的評論“Mark” 一下,謝謝您的支持

目錄合集

iOS面試題-常問多線程問題(六)

1.什么是多線程?

  • 多線程是指實現多個線程并發(fā)執(zhí)行的技術,進而提升整體處理性能。
  • 同一時間,CPU 只能處理一條線程,多線程并發(fā)執(zhí)行,其實是 CPU 快速的在多條線程之間調度(切換)如果 CPU 調度線程的時間足夠快, 就造成了多線程并發(fā)執(zhí)行的假象
    • 主線程的棧區(qū) 空間大小為1M,非常非常寶貴
    • 子線程的棧區(qū) 空間大小為512K內存空間
  • 優(yōu)勢
    充分發(fā)揮多核處理器的優(yōu)勢,將不同線程任務分配給不同的處理器,真正進入“并行計算”狀態(tài)
  • 弊端
    新線程會消耗內存控件和cpu時間,線程太多會降低系統(tǒng)運行性能。

2.進程和線程區(qū)別?

  • 進程:正在運行的程序,負責程序的內存分配,每一個進程都有自己獨立的虛擬內存空間。(一個程序運行的動態(tài)過程)
  • 線程:線程是進程中一個獨立執(zhí)行的路徑(控制單元)一個進程至少包含一條線程,即主線程可以將耗時的執(zhí)行路徑(如網絡請求)放在其他線程中執(zhí)行。
  • 進程和線程的比較
    • 線程是 CPU 調用的最小單位
    • 進程是 CPU 分配資源和調度的單位
    • 一個程序可以對應多個進程,一個進程中可有多個線程,但至少要有一條線程,
    • 同一個進程內的線程共享進程資源

3.線程間怎么通信?

  • 線程間的通信體現: 一個線程傳遞數據給另一個線程,
  • 在一個線程中執(zhí)行完特定的任務后,轉到另一個線程繼續(xù)執(zhí)行任務。

4.iOS的多線程方案有哪幾種?

5. 什么是GCD?

GCD(Grand Central Dispatch), 又叫做大中央調度, 它對線程操作進行了封裝,加入了很多新的特性,內部進行了效率優(yōu)化,提供了簡潔的C語言接口, 使用更加高效,也是蘋果推薦的使用方式.

6.GCD 的隊列類型?

GCD的隊列可以分為2大類型

  • 并發(fā)隊列(Concurrent Dispatch Queue
    可以讓多個任務并發(fā)(同時)執(zhí)行(自動開啟多個線程同時執(zhí)行任務)
    并發(fā)功能只有在異步(dispatch_async)函數下才有效

  • 串行隊列(Serial Dispatch Queue
    讓任務一個接著一個地執(zhí)行(一個任務執(zhí)行完畢后,再執(zhí)行下一個任務),按照FIFO順序執(zhí)行.

7.什么是同步和異步任務派發(fā)(synchronous和asynchronous)?

GCD多線程經常會使用 dispatch_syncdispatch_async函數向指定隊列添加任務,分別是同步和異步

  • 同步指阻塞當前線程,既要等待添加的耗時任務塊Block完成后,函數才能返回,后面的代碼才能繼續(xù)執(zhí)行
  • 異步指將任務添加到隊列后,函數立即返回,后面的代碼不用等待添加的任務完成后即可執(zhí)行,異步提交無法確定任務執(zhí)行順序

8.dispatch_after使用?

通過該函數可以讓提交的任務在指定時間后開始執(zhí)行,也就是延遲執(zhí)行;

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"10秒后開始執(zhí)行")
    });

9.dispatch_group_t (組調度)的使用?

組調度可以實現等待一組操都作完成后執(zhí)行后續(xù)任務.

dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //請求1
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //請求2
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //請求3
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    //界面刷新
    NSLog(@"任務均完成,刷新界面");
});

10.dispatch_semaphore (信號量)如何使用?

  • 用于控制最大并發(fā)數
  • 可以防止資源搶奪

與他相關的共有三個函數,分別是

dispatch_semaphore_create,  // 創(chuàng)建最大并發(fā)數
dispatch_semaphore_wait。    // -1 開始執(zhí)行 (0則等待)
dispatch_semaphore_signal,  // +1 

11.什么是NSOperation?

NSOperation是基于GCD的上封裝,將線程封裝成要執(zhí)行的操作,不需要管理線程的生命周期和同步,比GCD可控性更強

例如:
可以加入操作依賴控制執(zhí)行順序,設置操作隊列最大并發(fā)數,取消操作等

12. NSOperation如何實現操作依賴?

通過任務間添加依賴,可以為任務設置執(zhí)行的先后順序。接下來通過一個案例來展示設置依賴的效果。

NSOperationQueue *queue=[[NSOperationQueue alloc] init];
//創(chuàng)建操作
NSBlockOperation *operation1=[NSBlockOperation blockOperationWithBlock:^(){
    NSLog(@"執(zhí)行第1次操作,線程:%@",[NSThread currentThread]);
}];
NSBlockOperation *operation2=[NSBlockOperation blockOperationWithBlock:^(){
    NSLog(@"執(zhí)行第2次操作,線程:%@",[NSThread currentThread]);
}];
NSBlockOperation *operation3=[NSBlockOperation blockOperationWithBlock:^(){
    NSLog(@"執(zhí)行第3次操作,線程:%@",[NSThread currentThread]);
}];
//添加依賴
[operation1 addDependency:operation2];
[operation2 addDependency:operation3];
//將操作添加到隊列中去
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];

13.是否可以把比較耗時的操作放在 NSNotification中?

  • 如果在異步線程發(fā)的通知,那么可以執(zhí)行比較耗時的操作;
  • 如果在主線程發(fā)的通知,那么就不可以執(zhí)行比較耗時的操作

14.說幾個你在工作中使用到的線程安全的例子?

  • UIKit(必須在主線程)
  • FMDBDataBaseQueue(串行隊列)
  • 等等..

15.dispatch_barrier_(a)sync使用?

  • 一個dispatch barrier 允許在一個并發(fā)隊列中創(chuàng)建一個同步點。當在并發(fā)隊列中遇到一個barrier, 他會延遲執(zhí)行barrier的block,等待所有在barrier之前提交的blocks執(zhí)行結束。 這時,barrier block自己開始執(zhí)行。 之后, 隊列繼續(xù)正常的執(zhí)行操作。

16. dispatch_set_target_queue 使用?

dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);

dispatch_set_target_queue 函數有兩個作用:第一,變更隊列的執(zhí)行優(yōu)先級;第二,目標隊列可以成為原隊列的執(zhí)行階層。

  • 第一個參數是要執(zhí)行變更的隊列(不能指定主隊列和全局隊列)
  • 第二個參數是目標隊列(指定全局隊列)
    主線程是相對于什么而言的

17.在項目什么時候選擇使用 GCD,什么時候選 擇 NSOperation?

  • 項目中使用 NSOperation 的優(yōu)點是 NSOperation 是對線程的高度抽象,在項目中使 用它,會使項目的程序結構更好,子類化 NSOperation 的設計思路,是具有面向對 象的優(yōu)點(復用、封裝),使得實現是多線程支持,而接口簡單,建議在復雜項目中 使用。
  • 項目中使用 GCD 的優(yōu)點是 GCD 本身非常簡單、易用,對于不復雜的多線程操 作,會節(jié)省代碼量,而 Block 參數的使用,會是代碼更為易讀,建議在簡單項目中 使用。

18.說一下 OperationQueue 和 GCD 的區(qū)別,以及各自的優(yōu)勢

  1. GCD是純C語?言的API,NSOperationQueue是基于GCD的OC版本封裝
  2. GCD只?支持FIFO的隊列列,NSOperationQueue可以很?方便便地調整執(zhí)?行行順 序、設 置最?大并發(fā)數量量
  3. NSOperationQueue可以在輕松在Operation間設置依賴關系,?而GCD 需要寫很 多的代碼才能實現
  4. NSOperationQueue?支持KVO,可以監(jiān)測operation是否正在執(zhí)?行行 (isExecuted)、 是否結束(isFinished),是否取消(isCanceld)
  5. GCD的執(zhí)?行行速度?比NSOperationQueue快 任務之間不不太互相依賴:GCD 任務之間 有依賴\或者要監(jiān)聽任務的執(zhí)?行行情況:NSOperationQueue

19.GCD如何取消線程?

GCD目前有兩種方式可以取消線程:

1.dispatch_block_cancel類似NSOperation一樣,可以取消還未執(zhí)行的線程。但是沒辦法做到取消一個正在執(zhí)行的線程。

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_block_t block1 = dispatch_block_create(0, ^{
    NSLog(@"block1");
});
dispatch_block_t block2 = dispatch_block_create(0, ^{
    NSLog(@"block2");
});

dispatch_block_t block3 = dispatch_block_create(0, ^{
    NSLog(@"block3");
});

dispatch_async(queue, block1);
dispatch_async(queue, block2);
dispatch_async(queue, block3);
dispatch_block_cancel(block3); // 取消 block3

2.使用臨時變量+return 方式取消 正在執(zhí)行的Block

__block BOOL gcdFlag= NO; // 臨時變量
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    for (long i=0; i<1000; i++) {
        NSLog(@"正在執(zhí)行第i次:%ld",i);
        sleep(1);
        if (gcdFlag==YES) { // 判斷并終止
            NSLog(@"終止");
            return ;
        }
    };
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                   NSLog(@"我要停止啦");
                   gcdFlag = YES;
               });

20.NSOperation取消線程方式?

1.通過 cancel 取消未執(zhí)行的單個操作

NSOperationQueue *queue1 = [[NSOperationQueue alloc]init];
NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"block11");
}];
NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"block22");
}];
NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"block33");
}];
[block3 cancel];
[queue1 addOperations:@[block1,block2,block3] waitUntilFinished:YES];

2.移除隊列里面所有的操作,但正在執(zhí)行的操作無法移除

[queue1 cancelAllOperations];

3.掛起隊列,使隊列任務不再執(zhí)行,但正在執(zhí)行的操作無法掛起

queue1.suspended = YES;

4.我們可以自定義NSOperation,實現取消正在執(zhí)行的操作。其實就是攔截main方法。

 main方法:
 1、任何操作在執(zhí)行時,首先會調用start方法,start方法會更新操作的狀態(tài)(過濾操作,如過濾掉處于“取消”狀態(tài)的操作)。
 2、經start方法過濾后,只有正常可執(zhí)行的操作,就會調用main方法。
 3、重寫操作的入口方法(main),就可以在這個方法里面指定操作執(zhí)行的任務。
 4、main方法默認是在子線程異步執(zhí)行的。

21. 什么是線程安全?

  • 1塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源
  • 比如多個線程訪問同一個對象、同一個變量、同一個文件
  • 當多個線程訪問同一塊資源時,很容易引發(fā)數據錯亂和數據安全問題

22.線程安全的處理手段有哪些?

  • 加鎖
  • 同步執(zhí)行

23.如何理解GCD死鎖?

  • 所謂死鎖.通常是指2個操作相互等待對方完成,造成死循環(huán),于是2個操作都無法進行,就產生了死鎖;

24.自旋鎖和互斥鎖的是什么?

  • 自旋鎖會忙等: 所謂忙等,即在訪問被鎖資源時,調用者線程不會休眠,而是不停循環(huán)在那里,直到被鎖資源釋放鎖。
  • 互斥鎖會休眠: 所謂休眠,即在訪問被鎖資源時,調用者線程會休眠,此時cpu可以調度其他線程工作。直到被鎖資源釋放鎖。此時會喚醒休眠線程。

25.OC你了解的鎖有哪些?

  • os_unfair_lock ios10 開始
  • OSSpanLock ios10 廢棄
  • dispatch_semaphore 建議使用,性能也比較好
  • dispatch_mutex
  • dispatch_queue 串行
  • NSLock 對 mutex 封裝
  • @synchronized 性能最差

26:自旋和互斥什么情況下使用?

什么情況使用自旋鎖比較劃算?

  • 預計線程等待鎖的時間很短
  • 加鎖的代碼(臨界區(qū))經常被調用,但競爭情況很少發(fā)生
  • CPU資源不緊張
  • 多核處理器

什么情況使用互斥鎖比較劃算?

  • 預計線程等待鎖的時間較長
  • 單核處理器
  • 臨界區(qū)有IO操作
  • 臨界區(qū)代碼復雜或者循環(huán)量大
  • 臨界區(qū)競爭非常激烈

27.代碼分析一,此函數耗時? 輸出結果

dispatch_queue_t queue = dispatch_queue_create("test", nil);
dispatch_async(queue, ^{
    NSLog(@"1");
    sleep(1);
});
dispatch_async(queue, ^{
    NSLog(@"2");
    sleep(1);
});
dispatch_sync(queue, ^{
    NSLog(@"3");
    sleep(1);
});
此函數耗時?: 3秒
此函數輸出?: 123

  • 串行隊列異步執(zhí)行會開新線程,同步執(zhí)行不會開線程,在一個串行隊列了,則是按照順序執(zhí)行 耗時3秒 ,打印123;
  • 并發(fā): 任務以FIFO從序列中移除,然后并發(fā)運行,可以按照任何順序完成。它會自動開啟多個線程同時執(zhí)行任務
  • 串行: 任務以FIFO從序列中一個一個執(zhí)行。一次只調度一個任務,隊列中的任務一個接著一個地執(zhí)行(一個任務執(zhí)行完畢后,再執(zhí)行下一個任務)而且只會開啟一條線程

28.代碼分析二,打印結果

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
    NSLog(@"1");
    [self performSelector:@selector(test) withObject:nil afterDelay:0];
    NSLog(@"3");
});

- (void)test{
    NSLog(@"2");
}

打印 1,3
performSelector after 是基于 timer 定制器,定時器又是基于 runloop 實現的;任務2在子線程中,子線程默認 runloop 是不開啟的,所以不執(zhí)行2

29.請問下面代碼的打印結果是什么?

- (void)test{
    NSLog(@"2");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSThread *thread = [[NSThread alloc]initWithBlock:^{
        NSLog(@"1");
    }];
    [thread start];
    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}

打印1

  • start 執(zhí)行完,線程就銷毀了.任務 test 沒法執(zhí)行了

收錄 | 原文地址


結語

再次說一聲,對于答案,不一定都合適,歡迎大家積極討論;整理不易,如果您覺得還不錯,麻煩在文末 “點個贊” ,或者留下您的評論“Mark” 一下,謝謝您的支持


推薦文集

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容