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


1.進程與線程

  • 進程:

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

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

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

  • 線程

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

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

  • 進程和線程的關系

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

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

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

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

2.什么是多線程?

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

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

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

3.多線程的優點和缺點

  • 優點:

    能適當提高程序的執行效率

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

  • 缺點:

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

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

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

4.多線程的 並行 和 並發 有什么區別?

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

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

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

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

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

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

6.多個網絡請求完成后執行下一步

  • 使用GCD的dispatch_group_t

    創建一個dispatch_group_t

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

    當所有enter的block都leave后,會執行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信號量為基於計數器的一種多線程同步機制。如果semaphore計數大於等於1,計數-1,返回,程序繼續運行。如果計數為0,則等待。dispatch_semaphore_signal(semaphore)為計數+1操作,dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)為設置等待時間,這里設置的等待時間是一直等待。

    創建semaphore為0,等待,等10個網絡請求都完成了,dispatch_semaphore_signal(semaphore)為計數+1,然后計數-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.多個網絡請求順序執行后執行下一步

  • 使用信號量semaphore

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

     
NSString *str = @"http://www.jianshu.com/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.異步操作兩組數據時, 執行完第一組之后, 才能執行第二組

  • 這里使用dispatch_barrier_async柵欄方法即可實現

     
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(@"第一次任務, 第二次任務執行完畢, 繼續執行"); }); dispatch_async(queue, ^{ NSLog(@"第三次任務的主線程為: %@", [NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"第四次任務的主線程為: %@", [NSThread currentThread]); }); 

9.多線程中的死鎖?

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

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

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

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

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

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

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

10.GCD執行原理?

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

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

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

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

 

其他面試題篇章:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM