歡迎閱讀iOS探索系列(按序閱讀食用效果更加)程序員
- iOS探索 alloc流程
- iOS探索 內存對齊&malloc源碼
- iOS探索 isa初始化&指向分析
- iOS探索 類的結構分析
- iOS探索 cache_t分析
- iOS探索 方法的本質和方法查找流程
- iOS探索 動態方法解析和消息轉發機制
- iOS探索 淺嘗輒止dyld加載流程
- iOS探索 類的加載過程
- iOS探索 分類、類拓展的加載過程
- iOS探索 isa面試題分析
- iOS探索 runtime面試題分析
- iOS探索 KVC原理及自定義
- iOS探索 KVO原理及自定義
- iOS探索 多線程原理
- iOS探索 多線程之GCD應用
- iOS探索 多線程之GCD底層分析
- iOS探索 多線程之NSOperation
- iOS探索 多線程面試題分析
- iOS探索 八大鎖分析
前面四篇文章分別介紹了多線程原理
、GCD的應用
、GCD底層原理
、NSOperation
,本文將分析iOS面試中高頻的多線程面試題,但願各位看官都能答對(部份內容跟前幾篇文章有點重複)面試
技術方案 | 簡介 | 語言 | 線程生命週期 | 使用評率 |
---|---|---|---|---|
pthread | 一套通用的多線程API 適用於Unix/Linux/Windows等系統 跨平臺/可移植 使用難度大 |
C | 程序員管理 | 幾乎不用 |
NSThread | 使用更加面向對象 簡單易用,可直接操做線程對象 |
OC | 程序員管理 | 偶爾使用 |
GCD | 旨在替代NSThread等線程技術 充分利用設備的多核 |
C | 自動管理 | 常用 |
NSOperation | 基於GCD(底層是GCD) 比GCD多了一些更簡單實用的功能 使用更加面向對象 |
OC | 自動管理 | 常用 |
注意:若是使用NSThread的performSelector:withObject:afterDelay:
時須要添加到當前線程的runloop
中,由於在內部會建立一個NSTimer
數組
GCD
和NSOperation
的關係以下:安全
GCD
是面向底層的C語言的APINSOperation
是用GCD
封裝構建的,是GCD
的高級抽象GCD
和NSOperation
的對好比下:網絡
GCD
執行效率更高,並且因爲隊列中執行的是由block
構成的任務,這是一個輕量級的數據結構——寫起來更加方便GCD
只支持FIFO
的隊列,而NSOpration
能夠設置最大併發數、設置優先級、添加依賴關係等調整執行順序NSOpration
甚至能夠跨隊列設置依賴關係,可是GCD
只能經過設置串行隊列,或者在隊列內添加barrier
任務才能控制執行順序,較爲複雜NSOperation
支持KVO
(面向對象)能夠檢測operation是否正在執行、是否結束、是否取消
- 實際項目中,不少時候只會用到異步操做,不會有特別複雜的線程關係管理,因此蘋果推崇的是優化完善、運行快速的GCD
- 若是考慮異步操做之間的事務性、順序性、依賴關係,好比多線程併發下載,GCD須要寫更多的代碼來實現,而NSOperation已經內建了這些支持
- 無論是GCD仍是NSOperation,咱們接觸的都是任務和隊列,都沒有直接接觸到線程,事實上線程管理也的確不須要咱們操心,系統對於線程的建立、調度管理和釋放都作得很好;而NSThread須要咱們本身去管理線程的生命週期,還要考慮線程同步、加鎖問題,形成一些性能上的開銷
dispatch_get_main_queue()
回到主線程刷新UIdispatch_group
統一調度刷新UIdispatch_once
單例
中使用,一個類僅有一個實例且提供一個全局訪問點method-Swizzling
使用保證方法只交換一次dispatch_after
將任務延遲加入隊列柵欄函數
可用做同步鎖dispatch_semaphore_t
GCD
的最大併發數dispatch_source定時器
替代偏差較大的NSTimer
AFNetworking
、SDWebImage
等知名三方庫中的NSOperation
使用線程池大小
小於核心線程池大小
時
線程池大小
大於等於核心線程池大小
時
maximumPoolSize>corePoolSize
,將建立新的線程來執行任務飽和策略
去處理參數名 | 表明意義 |
---|---|
corePoolSize | 線程池的基本大小(核心線程池大小) |
maximumPool | 線程池的最大大小 |
keepAliveTime | 線程池中超過corePoolSize樹木的空閒線程的最大存活時間 |
unit | keepAliveTime參數的時間單位 |
workQueue | 任務阻塞隊列 |
threadFactory | 新建線程的工廠 |
handler | 當提交的任務數超過maxmumPoolSize與workQueue之和時, 任務會交給RejectedExecutionHandler來處理 |
飽和策略有以下四個:數據結構
AbortPolicy
直接拋出RejectedExecutionExeception異常來阻止系統正常運行CallerRunsPolicy
將任務回退到調用者DisOldestPolicy
丟掉等待最久的任務DisCardPolicy
直接丟棄任務柵欄函數兩個API的異同:多線程
dispatch_barrier_async
:能夠控制隊列中任務的執行順序dispatch_barrier_sync
:不只阻塞了隊列的執行,也阻塞了線程的執行柵欄函數注意點:併發
全局隊列
起不到柵欄函數的做用全局隊列
時因爲對全局隊列形成堵塞,可能導致系統其餘調用全局隊列的地方也堵塞從而致使崩潰(並非只有你在使用這個隊列)AFNetworking
作網絡請求時爲何不能用柵欄函數起到同步鎖堵塞的效果,由於AFNetworking
內部有本身的隊列多讀單寫功能指的是:能夠多個讀者同時讀取數據,而在讀的時候,不能寫入數據;在寫的過程當中不能有其餘寫者去寫。即讀者之間是併發的,寫者與其餘寫者、讀者之間是互斥的異步
- (id)readDataForKey:(NSString*)key {
__block id result;
dispatch_sync(_concurrentQueue, ^{
result = [self valueForKey:key];
});
return result;
}
- (void)writeData:(id)data forKey:(NSString*)key {
dispatch_barrier_async(_concurrentQueue, ^{
[self setValue:data forKey:key];
});
}
複製代碼
併發同步
獲取到值後返回給讀者
併發異步
則會先返回空的result 0x0
,再經過getter方法獲取到值dispatch_barrier_async
知足:等隊列中前面的讀寫任務都執行完了再來執行當前任務不一樣於NSOperation
中能夠經過maxConcurrentOperationCount
去控制併發數,GCD須要經過信號量才能達到效果async
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"當前%d----線程%@", i, [NSThread currentThread]);
// 打印任務結束後信號量解鎖
dispatch_semaphore_signal(sem);
});
// 因爲異步執行,打印任務會較慢,因此這裏信號量加鎖
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
--------------------輸出結果:-------------------
當前1----線程<NSThread: 0x600001448d40>{number = 3, name = (null)}
當前0----線程<NSThread: 0x60000140c240>{number = 6, name = (null)}
當前2----線程<NSThread: 0x600001448d40>{number = 3, name = (null)}
當前3----線程<NSThread: 0x60000140c240>{number = 6, name = (null)}
當前4----線程<NSThread: 0x60000140c240>{number = 6, name = (null)}
當前5----線程<NSThread: 0x600001448d40>{number = 3, name = (null)}
當前6----線程<NSThread: 0x600001448d40>{number = 3, name = (null)}
當前7----線程<NSThread: 0x60000140c240>{number = 6, name = (null)}
當前8----線程<NSThread: 0x600001448d40>{number = 3, name = (null)}
當前9----線程<NSThread: 0x60000140c240>{number = 6, name = (null)}
--------------------輸出結果:-------------------
複製代碼
在面試中更多會考驗開發人員對於指定場景的多線程知識,接下來就來看看一些綜合運用
int a = 0;
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a++;
});
}
複製代碼
Variable is not assignable (missing __block type specifier)
__block int a = 0;
block
會講到__block int a = 0;
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a++;
});
}
NSLog(@"%d", a);
複製代碼
0
嗎?
while
在,不知足條件就不會跳出循環1~4
嗎?
5
嗎?
6~∞
嗎?
分析:
a=0
,而後進行a++
異步併發
會開闢子線程並有可能超車完成
線程2
在a=0
執行a++
時,線程3
有可能已經完成了a++
使a=1
線程3
修改了a
致使線程2
中a
的值也發生了變化線程2
對已是a=1
進行a++
操做線程4
、線程5
、線程n
的存在
a=0
時操做a
a=4
線程6
開始操做了,可是它還沒執行完就跳到了下一次循環了開闢了線程7
開始a++
線程6
執行結束脩改a=5
以後來到while條件判斷
就會跳出循環I/O
輸出比較耗時,此時線程7又恰好完成了再打印,就會輸出大於5
異步併發
都比較聽話,恰好在a=5
時沒有子線程
5
若是尚未明白能夠在while循環中添加打印代碼
__block int a = 0;
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d————%@", a, [NSThread currentThread]);
a++;
});
}
NSLog(@"此時的%d", a);
複製代碼
打印信息證實while外面的打印已經執行,可是子線程仍是有可能在對a進行操做的
可能有的小夥伴說這種需求不存在,可是咱們只管解決即是了
此時咱們應該能想到一下幾種解決方案:
柵欄函數
和全局隊列
搭配使用會無效,須要更換隊列類型;dispatch_barrier_sync
會阻塞線程,影響性能dispatch_barrier_async
不能知足需求,它只能控制前面的任務執行完畢再執行柵欄任務(控制任務執行)但是異步柵欄執行也是在子線程中,當a=4
時會先繼續下一次循環添加任務到隊列中,再來異步執行柵欄任務(不能控制任務的添加)__block int a = 0;
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
while (a < 5) {
dispatch_async(queue, ^{
a++;
});
dispatch_barrier_async(queue, ^{});
}
NSLog(@"此時的%d", a);
sleep(1);
NSLog(@"此時的%d", a);
--------------------輸出結果:-------------------
此時的5
此時的17
--------------------輸出結果:-------------------
複製代碼
__block int a = 0;
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a++;
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
NSLog(@"此時的%d", a);
sleep(1);
NSLog(@"此時的%d", a);
--------------------輸出結果:-------------------
此時的5
此時的5
--------------------輸出結果:-------------------
複製代碼
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
[marr addObject:@(i)];
});
}
NSLog(@"%lu", marr.count);
複製代碼
- 你:輸出一個小於1000的數,由於for循環中是異步操做
- 面試官:回去等消息吧
- 而後你回去以後試了下大吃一驚——程序崩了
這是爲何呢?
其實跟綜合運用一
是同樣的道理——for循環異步時無數條線程訪問數組,形成了線程不安全
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
[marr addObject:@(i)];
});
}
NSLog(@"%lu", marr.count);
--------------------輸出結果:-------------------
998
--------------------輸出結果:-------------------
複製代碼
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
@synchronized (self) {
[marr addObject:@(i)];
}
});
}
NSLog(@"%lu", marr.count);
--------------------輸出結果:-------------------
997
--------------------輸出結果:-------------------
複製代碼
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
[marr addObject:@(i)];
});
dispatch_barrier_async(queue, ^{});
}
NSLog(@"%lu", marr.count);
複製代碼
單路千萬條,跳跳通羅馬——固然除了這三種還有其餘辦法
串行異步
是任務一個接一個執行,但那是隊列中的任務才知足執行規律1000
,能夠在隊列中執行@synchronized
是個好東西,簡單易用還有效,但也沒有知足咱們的需求100
@synchronized
效率很低綜合運用一
不一樣,本題中是for循環1000
,只須要在同一隊列中打印便可(柵欄函數的注意點)dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
[marr addObject:@(i)];
});
dispatch_barrier_async(queue, ^{});
}
dispatch_async(queue, ^{
NSLog(@"%lu", marr.count);
});
複製代碼
多線程在平常開發中佔有很多分量,同時面試中也是必問模塊。但只有基礎知識是一成不變的,綜合運用題稍有改動就是另一種類型的知識考量了,並且也有多種解決方案