大廠常問iOS面試題--多線程篇

1.進程與線程

  • 進程:

    1.進程是一個具有一定獨立功能的程序關于某次數(shù)據(jù)集合的一次運行活動,它是操作系統(tǒng)分配資源的基本單元.

    2.進程是指在系統(tǒng)中正在運行的一個應用程序,就是一段程序的執(zhí)行過程,我們可以理解為手機上的一個app.

    3.每個進程之間是獨立的,每個進程均運行在其專用且受保護的內存空間內,擁有獨立運行所需的全部資源

  • 線程

    1.程序執(zhí)行流的最小單元,線程是進程中的一個實體.

    2.一個進程要想執(zhí)行任務,必須至少有一條線程.應用程序啟動的時候,系統(tǒng)會默認開啟一條線程,也就是主線程

  • 進程和線程的關系

    1.線程是進程的執(zhí)行單元,進程的所有任務都在線程中執(zhí)行

    2.線程是 CPU 分配資源和調度的最小單位

    3.一個程序可以對應多個進程(多進程),一個進程中可有多個線程,但至少要有一條線程

    4.同一個進程內的線程共享進程資源

2.什么是多線程?

  • 多線程的實現(xiàn)原理:事實上,同一時間內單核的CPU只能執(zhí)行一個線程,多線程是CPU快速的在多個線程之間進行切換(調度),造成了多個線程同時執(zhí)行的假象。

  • 如果是多核CPU就真的可以同時處理多個線程了。

  • 多線程的目的是為了同步完成多項任務,通過提高系統(tǒng)的資源利用率來提高系統(tǒng)的效率。

3.多線程的優(yōu)點和缺點

  • 優(yōu)點:

    能適當提高程序的執(zhí)行效率

    能適當提高資源利用率(CPU、內存利用率)

  • 缺點:

    開啟線程需要占用一定的內存空間(默認情況下,主線程占用1M,子線程占用512KB),如果開啟大量的線程,會占用大量的內存空間,降低程序的性能

    線程越多,CPU在調度線程上的開銷就越大

    程序設計更加復雜:比如線程之間的通信、多線程的數(shù)據(jù)共享

4.多線程的 并行 和 并發(fā) 有什么區(qū)別?

  • 并行:充分利用計算機的多核,在多個線程上同步進行

  • 并發(fā):在一條線程上通過快速切換,讓人感覺在同步進行

5.iOS中實現(xiàn)多線程的幾種方案,各自有什么特點?

  • NSThread 面向對象的,需要程序員手動創(chuàng)建線程,但不需要手動銷毀。子線程間通信很難。

  • GCD c語言,充分利用了設備的多核,自動管理線程生命周期。比NSOperation效率更高。

  • NSOperation 基于gcd封裝,更加面向對象,比gcd多了一些功能。

6.多個網(wǎng)絡請求完成后執(zhí)行下一步

  • 使用GCD的dispatch_group_t

    創(chuàng)建一個dispatch_group_t

    每次網(wǎng)絡請求前先dispatch_group_enter,請求回調后再dispatch_group_leave,enter和leave必須配合使用,有幾次enter就要有幾次leave,否則group會一直存在。

    當所有enter的block都leave后,會執(zhí)行dispatch_group_notify的block。

    NSString *str = @"http://xxxx.com/";
    NSURL *url = [NSURL URLWithString:str];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    
    dispatch_group_t downloadGroup = dispatch_group_create();
    for (int i=0; i<10; i++) {
        dispatch_group_enter(downloadGroup);
    
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"%d---%d",i,i);
            dispatch_group_leave(downloadGroup);
        }];
        [task resume];
    }
    
    dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
        NSLog(@"end");
    });
    
    
  • 使用GCD的信號量dispatch_semaphore_t

    dispatch_semaphore信號量為基于計數(shù)器的一種多線程同步機制。如果semaphore計數(shù)大于等于1,計數(shù)-1,返回,程序繼續(xù)運行。如果計數(shù)為0,則等待。dispatch_semaphore_signal(semaphore)為計數(shù)+1操作,dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)為設置等待時間,這里設置的等待時間是一直等待。

    創(chuàng)建semaphore為0,等待,等10個網(wǎng)絡請求都完成了,dispatch_semaphore_signal(semaphore)為計數(shù)+1,然后計數(shù)-1返回

    NSString *str = @"http://xxxx.com/";
    NSURL *url = [NSURL URLWithString:str];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    for (int i=0; i<10; i++) {
    
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"%d---%d",i,i);
            count++;
            if (count==10) {
                dispatch_semaphore_signal(sem);
                count = 0;
            }
        }];
        [task resume];
    }
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"end");
    });
    
    

7.多個網(wǎng)絡請求順序執(zhí)行后執(zhí)行下一步

  • 使用信號量semaphore

    每一次遍歷,都讓其dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER),這個時候線程會等待,阻塞當前線程,直到dispatch_semaphore_signal(sem)調用之后

    NSString *str = @"http://www.itdecent.cn/p/6930f335adba";
    NSURL *url = [NSURL URLWithString:str];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    for (int i=0; i<10; i++) {
    
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
            NSLog(@"%d---%d",i,i);
            dispatch_semaphore_signal(sem);
        }];
    
        [task resume];
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    }
    
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"end");
    });
    
    

8.異步操作兩組數(shù)據(jù)時, 執(zhí)行完第一組之后, 才能執(zhí)行第二組

  • 這里使用dispatch_barrier_async柵欄方法即可實現(xiàn)

    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"第一次任務的主線程為: %@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"第二次任務的主線程為: %@", [NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"第一次任務, 第二次任務執(zhí)行完畢, 繼續(xù)執(zhí)行");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"第三次任務的主線程為: %@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"第四次任務的主線程為: %@", [NSThread currentThread]);
    });
    
    

9.多線程中的死鎖?

死鎖是由于多個線程(進程)在執(zhí)行過程中,因為爭奪資源而造成的互相等待現(xiàn)象,你可以理解為卡主了。產生死鎖的必要條件有四個:

  • 互斥條件 : 指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程占用。如果此時還有其它進程請求資源,則請求者只能等待,直至占有資源的進程用畢釋放。

  • 請求和保持條件 : 指進程已經(jīng)保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程占有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。

  • 不可剝奪條件 : 指進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。

  • 環(huán)路等待條件 : 指在發(fā)生死鎖時,必然存在一個進程——資源的環(huán)形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1占用的資源;P1正在等待P2占用的資源,……,Pn正在等待已被P0占用的資源。

    最常見的就是 同步函數(shù) + 主隊列 的組合,本質是隊列阻塞。

    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    
    NSLog(@"1");
    // 什么也不會打印,直接報錯
    
    

10.GCD執(zhí)行原理?

  • GCD有一個底層線程池,這個池中存放的是一個個的線程。之所以稱為“池”,很容易理解出這個“池”中的線程是可以重用的,當一段時間后這個線程沒有被調用胡話,這個線程就會被銷毀。注意:開多少條線程是由底層線程池決定的(線程建議控制再3~5條),池是系統(tǒng)自動來維護,不需要我們程序員來維護(看到這句話是不是很開心?) 而我們程序員需要關心的是什么呢?我們只關心的是向隊列中添加任務,隊列調度即可。

  • 如果隊列中存放的是同步任務,則任務出隊后,底層線程池中會提供一條線程供這個任務執(zhí)行,任務執(zhí)行完畢后這條線程再回到線程池。這樣隊列中的任務反復調度,因為是同步的,所以當我們用currentThread打印的時候,就是同一條線程。

  • 如果隊列中存放的是異步的任務,(注意異步可以開線程),當任務出隊后,底層線程池會提供一個線程供任務執(zhí)行,因為是異步執(zhí)行,隊列中的任務不需等待當前任務執(zhí)行完畢就可以調度下一個任務,這時底層線程池中會再次提供一個線程供第二個任務執(zhí)行,執(zhí)行完畢后再回到底層線程池中。

  • 這樣就對線程完成一個復用,而不需要每一個任務執(zhí)行都開啟新的線程,也就從而節(jié)約的系統(tǒng)的開銷,提高了效率。在iOS7.0的時候,使用GCD系統(tǒng)通常只能開5--8條線程,iOS8.0以后,系統(tǒng)可以開啟很多條線程,但是實在開發(fā)應用中,建議開啟線程條數(shù):3--5條最為合理。


需要iOS開發(fā)學習資料、大廠面試真題,可加 iOS技術探討群:937194184,群文件直接獲取

如下圖所示:

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容