一侦厚、知識結(jié)構(gòu)分析
備注:
從上到下耻陕,更加面向?qū)ο螅簿透菀资褂谩?p>
多線程之間的關(guān)系
pthread是
POSIX
線程的APINSThread是Cocoa對pthread的封裝
GCD和NSOperationQueue是基于隊列的并發(fā)API
GCD是基于pthread和queue實現(xiàn)的
NSOperationQueue是對GCD的高級封裝
GCD
NSOperation
AFNetWorking + SDWebImage框架多有使用
問題1:AFNetWorking為什么使用NSOperation刨沦,為什么不使用GCD诗宣?
解釋:
此道題目也可以理解為NSOperation比GCD的有點。
1已卷、NSOperation是基于GCD更高一層的封裝梧田,完全面向?qū)ο螅槐菺CD更加簡單易用侧蘸,代碼可讀性高裁眯。
2、 FIFO隊列讳癌,而NSOperationQueue中的隊列可以被重新設(shè)置優(yōu)先級穿稳,從而實現(xiàn)不同操作的執(zhí)行順序調(diào)整。CGD不具備
3晌坤、添加操作之間的依賴關(guān)系逢艘,方便的控制執(zhí)行順序;GCD不具備骤菠。
4它改、可以很方便的取消一個操作的執(zhí)行;CGD不具備商乎。
5央拖、使用 KVO 觀察對操作執(zhí)行狀態(tài)的更改;CGD不具備鹉戚。
-
NSThread
用于實現(xiàn)一個常駐線程
問題2:常駐線程有什么作用鲜戒?
解釋:
通常情況下,創(chuàng)建子線程抹凳,在里面執(zhí)行任務(wù)遏餐,任務(wù)完成后,子線程會立刻銷毀赢底;如果需要經(jīng)常子線程中操作任務(wù)失都,那么頻繁的創(chuàng)建和銷毀子線程會造成資源的浪費。
所以需要常駐線程幸冻。
-
多線程和鎖
線程同步和資源共享
二粹庞、GCD
主要結(jié)構(gòu)如下:
- 同步/異步、串行/并發(fā)
- dispatch_barrier_async
- dispatch_group
2.1嘁扼、同步/異步信粮、串行/并發(fā)
dispatch_sync(serial_queue,^{ //任務(wù) })趁啸;
dispatch_async(serial_queue强缘,^{ //任務(wù) })督惰;
dispatch_sync(concurrent_queue,^{ //任務(wù) })
dispatch_async(concurrent_queue旅掂,^{ //任務(wù) })
2.1.1赏胚、同步串行
問題3:主隊列同步
解釋:
是隊列引起的循環(huán)等待,不是線程引起的循環(huán)等待商虐。
詳細解釋如下:
1觉阅、ios中默認會有一個主隊列、主線程秘车。
2典勇、主隊列是一個串行隊列。
3叮趴、viewDidLoad在主隊列中割笙,可以看成一個任務(wù)1。
4眯亦、Block相當于在主隊列添加任務(wù)2伤溉。
5、viewDidLoad在主線程運行妻率。
6乱顾、dispatch_sync說明任務(wù)2也在主線程運行。
7宫静、任務(wù)1完成后才能執(zhí)行任務(wù)2走净。
但是任務(wù)1還沒有完成,就開始執(zhí)行任務(wù)2囊嘉,任務(wù)2有依賴任務(wù)1的完成温技,任務(wù)1依賴任務(wù)2的完成革为,造成死鎖扭粱。
問題4:主隊列異步
沒有問題
解釋:
雖然沒有問題,但是主隊列提交的任務(wù)震檩,無論通過同步/異步方式琢蛤,都要在主線程進行處理!E茁病博其!
問題5:串行隊列同步
解釋:
不會有問題。
詳細解釋如下:
1迂猴、iOS默認會有一個主隊列慕淡、主線程。
2沸毁、主隊列是一個串行隊列峰髓。
3傻寂、viewDidLoad在主隊列中,可以看成一個任務(wù)1携兵。
4疾掰、Block是在另一個串行隊列中,可以看成任務(wù)2徐紧。
5静檬、viewDidLoad在主線程運行。
6并级、dispatch_sync說明任務(wù)2也在主線程運行拂檩。
7、因為二者不是在同一個隊列嘲碧,不會存在死鎖广恢,但是任務(wù)2會延遲任務(wù)1執(zhí)行。
2.2.2呀潭、同步并發(fā)
問題6:下面代碼輸出結(jié)果是:
解釋
1钉迷、iOS默認會有一個主隊列、主線程钠署。
2糠聪、主隊列是一個串行隊列。
3谐鼎、viewDidLoad在主隊列中舰蟆,可以看成一個任務(wù)1。
4狸棍、global_queue是全局并發(fā)隊列身害,里面有任務(wù)2和任務(wù)3
5、viewDidLoad在主線程運行草戈。
6塌鸯、dispatch_sync說明global_queue中的任務(wù)也在主線程運行(會阻斷線程,強制執(zhí)行自己的)唐片。
7丙猬、因為global_queue和主線程隊列不是同一個隊列,不會造成死鎖费韭。
8茧球、因為global_queue是全局并發(fā)隊列,一個任務(wù)不用管前面的任務(wù)是否執(zhí)行完畢星持。所以任務(wù)2未完成時抢埋,可以執(zhí)行任務(wù)3,然后執(zhí)行任務(wù)2,都是在主線程執(zhí)行揪垄。
2.2.3鲤屡、異步串行
這段代碼是經(jīng)常使用的
代碼分析:
1、ios中默認會有一個主隊列福侈、主線程酒来。
2、主隊列是一個串行隊列肪凛。
3堰汉、viewDidLoad在主隊列中,可以看成一個任務(wù)1伟墙。
4翘鸭、Block相當于在主隊列添加任務(wù)2。
5戳葵、viewDidLoad在主線程運行就乓。
6、dispatch_async說明任務(wù)2在子線程運行拱烁,也就是不會阻擋任務(wù)1的運行生蚁。
7、任務(wù)1完成后才能執(zhí)行任務(wù)2戏自。
因為任務(wù)1在子線程運行邦投,不會阻擋任務(wù)2,所以正常使用擅笔。
2.2.4志衣、異步并發(fā)
問題7:以下代碼輸出結(jié)果:
解釋
1、global_queue是全局隊列猛们,采用dispatch_async念脯,所以會開辟一個子線程。
2弯淘、子線程的runLoop默認是不開啟的绿店,而performSelector:withObject:afterDelay是在沒有runloop的情況下會失效,所以此方法不執(zhí)行耳胎。
3惯吕、打印結(jié)果13惕它。
2.3怕午、dispatch_barrier_async()
2.3.1、場景
問題8:怎樣利用CGD實現(xiàn)多讀單寫淹魄?
利用CGD提供的柵欄函數(shù)
解析:
- 讀者郁惜、讀者并發(fā)
- 讀者、寫者互斥
- 寫者、寫者互斥
可以理解為:
1兆蕉、讀處理之間是并發(fā)的羽戒,肯定要用并發(fā)隊列。
因為讀取操作虎韵,往往需要立刻返回結(jié)果易稠,故采用同步。
這些讀處理允許在多個子線程包蓝。
2驶社、寫處理時候,其余操作都不能執(zhí)行测萎。利用柵欄函數(shù)亡电,異步操作。利用柵欄函數(shù)異步操作的原因:柵欄函數(shù)同步操作會阻塞當前線程硅瞧,如果當前線程還有其它操作份乒,則會影響用戶體驗。
核心代碼如下:
@interface UserCenter()
{
// 定義一個并發(fā)隊列
dispatch_queue_t concurrent_queue;
// 用戶數(shù)據(jù)中心, 可能多個線程需要數(shù)據(jù)訪問
NSMutableDictionary *userCenterDic;
}
@end
// 多讀單寫模型
@implementation UserCenter
- (id)init
{
self = [super init];
if (self) {
// 通過宏定義 DISPATCH_QUEUE_CONCURRENT 創(chuàng)建一個并發(fā)隊列
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];
});
}
2.3.2腕唧、dispatch_barrier_sync和dispatch_barrier_async區(qū)別
共同點:
- 它們前面的任務(wù)先執(zhí)行完或辖。
- 它們的任務(wù)執(zhí)行完,再執(zhí)行后面的任務(wù)枣接。
不同點:
- dispatch_barrier_sync會阻止當前線程孝凌,等它的任務(wù)執(zhí)行完畢,才能往下進行月腋。
- dispatch_barrier_async不會阻塞當前線程蟀架,允許其它非當前隊列的任務(wù)繼續(xù)執(zhí)行。
注意:
使用柵欄函數(shù)時榆骚,使用自定義隊列才有意義片拍,如果使用串行隊列/系統(tǒng)的全局并發(fā)隊列,這個柵欄函數(shù)就相當于一個同步函數(shù)
2.3妓肢、dispatch_group
問題9:使用CGD實現(xiàn)這個需求:A捌省、B、C三個任務(wù)并發(fā)碉钠,完成后執(zhí)行任務(wù)D纲缓。
// 創(chuàng)建一個group
dispatch_group_t group = dispatch_group_create();
// 異步組分派到并發(fā)隊列當中
dispatch_group_async(group, concurrent_queue, ^{
});
//監(jiān)聽
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 當添加到組中的所有任務(wù)執(zhí)行完成之后會調(diào)用該Block
});
三、NSOperation
3.1喊废、NSOperation優(yōu)點
- 添加任務(wù)依賴
- 任務(wù)執(zhí)行狀態(tài)控制
- 最大并發(fā)量(maxConcurrentOperationCount)
問題10:我們可以控制任務(wù)的哪些狀態(tài)祝高?
-
isReady
當前任務(wù)是否處于就緒狀態(tài) -
isExecuting
當前任務(wù)是否正在執(zhí)行 -
isFinished
當前任務(wù)是否執(zhí)行完成 -
isCancelled
當前任務(wù)是否被標記為取消(不是判斷是否被取消,是標記)
是通過KVO進行控制的污筷。
3.2工闺、狀態(tài)控制
問題11:我們怎么控制NSOperation的狀態(tài)
- 如果只重寫了main方法,底層控制任務(wù)執(zhí)行狀態(tài)以及任務(wù)退出。
- 如果重寫了start方法陆蟆,自行控制任務(wù)狀態(tài)雷厂。
問題12:系統(tǒng)是怎樣移除一個isFinished=YES的NSOperation的?
- 通過KVO
小結(jié):
NSOperation: 主隊列默認在主線程執(zhí)行叠殷,自定義隊列默認在后臺執(zhí)行(會開辟子線程)改鲫。
四、NSThread
4.1林束、啟動流程
1钩杰、調(diào)用start()方法、啟動線程诊县。
2讲弄、在start()內(nèi)部會創(chuàng)建一個pthread線程,指定pthread線程的啟動函數(shù)依痊。
3避除、在啟動函數(shù)中會調(diào)用NSThread定義的main()函數(shù)。
4胸嘁、在main()函數(shù)中會調(diào)用performSelector:函數(shù)瓶摆,來執(zhí)行我們創(chuàng)建的函數(shù)。
5性宏、指定函數(shù)運行完成群井,會調(diào)用exit()函數(shù),退出線程毫胜。
4.2书斜、常駐線程
參考RunLoop
五、多線程與鎖
問題13:iOS中都有哪些鎖酵使,你是怎樣使用的荐吉?
解釋:
5.1、@synchronized(互斥鎖) ??????
- 一般在創(chuàng)建單例對象的時候使用口渔,保證在多線程環(huán)境下样屠,創(chuàng)建的單例對象是唯一的。
5.2缺脉、 atomic(自旋鎖)??????
- 屬性關(guān)鍵字
- 對被修飾對象進行原子操作(不負責使用)
備注:
原子操作:不會被線程調(diào)度打斷的操作痪欲;這種操作一旦開始,就一直運行到結(jié)束攻礼,中間不會切換到另一個線程业踢。
不負責使用:屬性賦值時候,能夠保證線程安全秘蛔;對屬性進行操作陨亡,不能保證線程安全傍衡。
例如:
@property (atomic) NSMutableArray *array;
self.array = [NSMutableArray array];//線程安全
[self.array addObject:obj];//線程不安全
5.3深员、 OSSpinLock(自旋鎖)
- 循環(huán)等待訪問负蠕,不釋放當前資源。
- 用于輕量級數(shù)據(jù)訪問倦畅,簡單的int值 +1/-1操作遮糖。
- 使用場景:
- 內(nèi)存引用計數(shù)加1或減1
- runtime也有使用到。
5.4叠赐、 NSLock(互斥鎖)
螞蟻金服面試題:
解釋:
對同一把鎖兩次調(diào)用欲账,由于重入的原因會造成死鎖;解決辦法就是使用遞歸鎖(可以重入)芭概。
5.5赛不、 NSRecursiveLock(遞歸鎖)(互斥鎖)
5.6、 dispatch_semaphore_t(信號量)??????
-
dispatch_semaphore_create(5)
創(chuàng)建信號量罢洲,指定最大并發(fā)數(shù) -
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
等待信號量>=1踢故。
如果當前信號量>=1,信號量減1惹苗,程序繼續(xù)執(zhí)行殿较。
如果信號量<=0,原地等待桩蓉,不允許程序往下執(zhí)行淋纲。 -
dispatch_semaphore_signal(semaphore)
程序執(zhí)行完畢,發(fā)送信號院究,釋放資源洽瞬,信號量加1。
5.6.1业汰、dispatch_semaphore_create
5.6.2片任、dispatch_semaphore_wait
5.6.3、dispatch_semaphore_signal
小結(jié):
1蔬胯、鎖分為互斥鎖对供、自旋鎖
2、互斥鎖和自旋鎖的區(qū)別
自旋鎖: 忙等待氛濒。即在訪問被鎖資源時产场,調(diào)用者線程不會休眠,而是不停循環(huán)在那里舞竿,直到被鎖資源釋放
互斥鎖: 會休眠京景。即在訪問被鎖資源時,調(diào)用者線程會休眠骗奖,此時cpu可以調(diào)度其它線程工作确徙,直到被鎖資源釋放醒串,此時會喚醒休眠線程。
問題14:iOS系統(tǒng)為我們提供了幾種多線程技術(shù)鄙皇?各自有什么特點芜赌?
解釋:
GCD
用于一些簡單的線程同步,包括子線程分派伴逸;還有就是解決類似于多讀單寫功能缠沈。NSOperation及NSOperationQueue
可以方便控制任務(wù)狀態(tài)、添加依賴/移除依賴错蝴;多用于復雜線程控制:AFNetWorking和SDWebImage洲愤。NSThread
往往用于實現(xiàn)一個常駐線程。
總結(jié):
概念理解
隊列概念
隊列是任務(wù)的容器-
串行隊列顷锰、并發(fā)隊列區(qū)別
串行隊列: 一次僅僅調(diào)度一個任務(wù)柬赐,隊列中的任務(wù)一個個執(zhí)行姨蟋。(一個任務(wù)完成后龄砰,再運行下一個任務(wù))。
遵循FIFO原則:先進先出棵逊、后進后出万矾。并發(fā)隊列:不需要把一個任務(wù)完成后悼吱,再運行下一個任務(wù)。
仍然遵循FIFO原則良狈,只是不需要等待任務(wù)完成后添。 并行、并發(fā)區(qū)別
并行:同一時刻薪丁,多條指令在多個處理器同時執(zhí)行遇西。
并發(fā):同一個時刻,只能處理一條指令严嗜,但是多個指令被快速的輪換執(zhí)行粱檀,達到了具有同時執(zhí)行的效果。異步漫玄、同步區(qū)別
異步: 可以開啟新的線程茄蚯。
同步: 不可以開啟新的線程,在當前線程運行睦优。
同步異步渗常、串行并行形象理解
這兩對概念單獨看起來,明白怎么回事汗盘;但是皱碘,一旦運用起來,總是不能得心應手隐孽“┐唬總的來說健蕊,就是不能將概念熟記于心,缺乏形象概念踢俄。
下面采用圖解 + 文字進行表述:
一個隊列(串行+并發(fā))好比一個容器缩功。
執(zhí)行代碼好比一個個任務(wù)。
同步異步好比任務(wù)的標簽褪贵。
容器里面裝有好多個打有標簽的任務(wù)掂之。
線程好比流水線的傳送帶抗俄,
所有的工作都是CPU在做脆丁,姑且將CPU比做操作工。
代碼運行的時候动雹,大家想象工廠的流水線的工作場景:
1槽卫、從容器(隊列)中取出任務(wù)(執(zhí)行代碼),放到傳送帶上胰蝠。
如果容器是串行隊列歼培,則完成一個,取出一個茸塞。
如果容器是并發(fā)隊列躲庄,則一直不停的投放。
2钾虐、任務(wù)(執(zhí)行代碼)放到傳送帶(線程)的一剎那噪窘,CPU(操作工)看了一眼上面的標簽:如果標簽是同步,就將它放到當前傳送帶效扫;如果標簽是異步倔监,就新增加一條傳送帶,然后把任務(wù)放上去(理解操作工無所不能菌仁,可以隨意增加傳送帶)浩习。
上面只是一個形象的比喻,加深對多線程理解济丘。
小結(jié):
從上面的分析可知:
1谱秽、串行隊列任務(wù)之間相互包含,容易造成死鎖摹迷;并發(fā)隊列則不會疟赊。這種死鎖稱為隊列死鎖。
2泪掀、并發(fā)隊列+異步听绳,才會有多線程效果。
如果只有當前一個線程可以利用异赫,并發(fā)隊列中任務(wù)雖然可以快速取出分派椅挣,奈何只有一個線程(主干道)头岔,只能一個個排隊執(zhí)行。