基本概念
線程:一個應(yīng)用的運行是一個進程偶房,一個進程中可以開啟多條線程執(zhí)行不同的任務(wù)诱建;線程是CPU分配和調(diào)度資源的最小單位骇陈;多線程并發(fā)其實是GPU的快速切換調(diào)度處理的假象 / 但并行是充分利用多核在多個線程上同步進行漩绵;可以提高CPU利用率和運行效率嘁圈,但是過多時會占用內(nèi)存空間殉农,降低性能刀脏。
任務(wù):狹義上指的閉包內(nèi)的代碼(如GCD的block中),分sync和async超凳。
隊列:存放待執(zhí)行任務(wù)的等待隊列愈污,F(xiàn)IFO原則耀态,分串行和并行。
線程同步:加鎖暂雹;串行隊列首装;NSOperation的最大并發(fā)數(shù)=1;各種Lock杭跪;信號量簿盅;CGD的barrierAPI。
多線程的優(yōu)缺點:提升效率揍魂,充分利用多核特性桨醋;但創(chuàng)建線程需要資源消耗(子線程內(nèi)存占用約512kb);切換線程需要消耗CPU现斋。
GCD
原理:時間片的輪換喜最。讓任務(wù)在線程中排隊,根據(jù)可用資源安排內(nèi)核處理庄蹋,底層也是用線程實現(xiàn)瞬内,但是自動管理線程的聲明周期,不用關(guān)心線程具體的使用情況限书。
源碼中的隊列
1個主線程管理池+1個其他線程管理池+14個runloop隊列虫蝶。其中12個可以通過各種API獲取:create(優(yōu)先級倦西,overcommit能真,串行并行)組合獲取。
根據(jù)qos扰柠,overcommit的組合粉铐,root queue一共12個。
主線程和主隊列
主隊列任務(wù)一定在主線程執(zhí)行卤档,但為了線程切換造成的性能消耗蝙泼,主線程有空時可能會執(zhí)行其他隊列的任務(wù)(CPU的寄存器)。注:在GCD中我們永遠無法直接接觸到線程劝枣,而是根據(jù)隊列和任務(wù)的組合選用汤踏,讓系統(tǒng)自動對線程進行管理調(diào)度。
常用API
dispatch_queue_create創(chuàng)建隊列(SERIAL串行舔腾,CONCURRENT并行)
dispatch_get_main_queue()獲取主隊列
dispatch_get_global_queue全局隊列(入?yún)os優(yōu)先級+overcommit是否過量開線程)
dispatch_after延遲把任務(wù)加入某隊列
dispatch_barrier_async柵欄方法控制(自定義并行隊列中分割任務(wù)執(zhí)行順序)
dispatch_once創(chuàng)建單例(保證程序中只執(zhí)行一次)
dispatch_semaphore_create / signal / wait創(chuàng)建信號量(顆粒度更細的任務(wù)順序控制溪胶,即加鎖原理)
注1:overcommit,串行默認(rèn)true表示內(nèi)存不足也得開線程完成串行任務(wù)琢唾,并行false载荔。
注2:barrier_sync和barrier_async都是等前面的執(zhí)行完再執(zhí)行自己的任務(wù),再執(zhí)行后面的任務(wù)采桃。區(qū)別就是是否等待自己的任務(wù)執(zhí)行完懒熙,再把后面的任務(wù)加入隊列(但都不會執(zhí)行)丘损。
dispatch_once
多用于創(chuàng)建單例和方法交換。
單例原理:dispatch_once_t是個整型工扎,外部初始化標(biāo)記徘钥,dispatch_once(&onceToken)在初始化的標(biāo)記下,才會執(zhí)行Block肢娘,執(zhí)行后標(biāo)記掉dispatch_atomic_cmpxchg { block; dispatch_atomic_barrier內(nèi)存屏障呈础,加鎖原子操作賦值onceToken }(所以要傳地址),barrier實現(xiàn)原子操作保證線程安全橱健,標(biāo)記之后再次進入會調(diào)用_dispatch_hardware_pause以節(jié)省CPU而钞。
注意事項:單例的block調(diào)用期間,多次請求同類的dispatch_once會造成請求鏈表無限增長拘荡,造成死鎖臼节。
單例的弊端:濫用單例會浪費資源占用內(nèi)存;沒有抽象層接口珊皿,難以擴展网缝;職責(zé)過重未被單一職責(zé);可能被回收造成狀態(tài)丟失蟋定。
死鎖
核心原因:任務(wù)的相互等待粉臊,如單例多次調(diào)用,串行隊列追加同步任務(wù)驶兜。
具體原因:首先得是個同步任務(wù)操作(即sync一個任務(wù))扼仲,然后要sync到一個當(dāng)前隊列(別的隊列肯定不阻塞啦),最后這個隊列要串行的(并行的當(dāng)然不阻塞啦)促王,典型的就是主線程sync犀盟。
總結(jié):當(dāng)前隊列(串行)追加同步任務(wù)(sync)。
注1:由于主隊列的任務(wù)一定在主線程上執(zhí)行蝇狼,所以在主線程往主隊列追加任務(wù),就是往主線程同步任務(wù)倡怎,會阻塞主線程迅耘,造成死鎖。
注2:死鎖其實是隊列任務(wù)互相等待引起的监署,而與線程無關(guān)颤专。比如自建兩個隊列,嵌套sync钠乏,雖然是在同一線程執(zhí)行(因為是sync)栖秕,但因為隊列不同所以不會造成死鎖。
其他多線程方案
pthread:C語言跨平臺的多線程API
NSThread:面向?qū)ο蟮妮p量級多線程方案晓避,手動管理生命周期簇捍,適合簡單的場景只壳。
NSOpertaion+NSOperationQueue:對GCD的封裝,面向?qū)ο笫钏埽稍O(shè)置跨隊列的依賴關(guān)系吼句。
線程同步
為了防止多個線程搶奪同一個資源造成的數(shù)據(jù)安全問題,給線程加鎖的操作事格。
原子操作:atomic修飾符-getter惕艳、setter加鎖。
OSMemoryBarrier:內(nèi)存屏障確保書寫順序驹愚。
Volatile:修飾變量远搪,告訴編譯器變量從內(nèi)存而非寄存器讀取。
其他加鎖:OSSpinLock自旋鎖逢捺,pthread_mutex互斥鎖终娃,@synchronize同步鎖,dispatch_semaphore信號量加鎖蒸甜。
同步執(zhí)行
GCD:將操作放入自建隊列(串行)中棠耕。
NSOperation:任務(wù)放入自建隊列并將最大并發(fā)數(shù)設(shè)置為1,設(shè)置跨隊列依賴addDependency柠新。
barrier_sync:柵欄方法窍荧,并發(fā)隊列中先執(zhí)行之前的。
semphore:信號量恨憎,收到signal+1蕊退,執(zhí)行wait-1,<=0等待憔恳。
------舊版------
關(guān)鍵字:多線程原理瓤荔,線程(偏CPU),隊列(串行并行)钥组,任務(wù)(同步異步)输硝,GCD及其源碼分析,@synchronized
概述
一個應(yīng)用的運行是一個進程程梦,一個進程中可以開啟多條線程用于執(zhí)行不同的任務(wù)点把,提高程序執(zhí)行效率,但線程過多會占用大量內(nèi)存空間屿附,降低性能郎逃。iOS中一般將UI事件的處理放在主線程里。一些耗時的操作不應(yīng)放入主線程挺份,應(yīng)新開線程異步執(zhí)行褒翰。
原理:CPU只能處理一條線程,多線程實際上是CPU快速在多條線程間不斷切換調(diào)度,而切換調(diào)度的時間特別快优训,造成了并發(fā)處理的假象朵你。
隊列與線程:隊列是對線程的包裝,便于使用型宙,偏程序(線程偏CPU)撬呢。隊列的底層也是通過線程實現(xiàn)的。
任務(wù)(狹義的閉包含義妆兑,非進程層級的廣義任務(wù)):根據(jù)是否開辟新線程魂拦,任務(wù)分為同步和異步,區(qū)別為是否阻塞當(dāng)前線程搁嗓。
iOS中的多線程
pthread
一套C語言編寫的通用的跨平臺的多線程API芯勘,iOS中不常用。忽略腺逛。
NSThread
面向?qū)ο蟮妮p量級的多線程方案荷愕,更直觀的控制線程對象,需手動管理生命周期棍矛,但多適用于比較簡單的場景安疗。
創(chuàng)建
1、實例方法創(chuàng)建(不需要start立刻創(chuàng)建線程)
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
2够委、performSelector創(chuàng)建(swift中取消了performSelector:方法)
[self performSelectorInBackground:@selector(SEL) withObject:nil];
3荐类、類方法創(chuàng)建(需手動start,可在線程開始前配置stack大小和優(yōu)先級)
NSThread * myThread = [[NSThread alloc] initWithTarget:(id)target selector:(SEL)selector object:(id)argument];
[myThread start];
屬性和用法
@property (readonly, getter=isExecuting) BOOL executing;
@property?(readonly,?getter=isFinished)?BOOL?finished;
@property?(readonly,?getter=isCancelled)?BOOL?cancelled;
@property (nullable, copy) NSString *name;
- (void)start;
- (void)cancel;
+ (void)exit;
+ (NSThread *)mainThread;
+ (NSThread *)currentThread;
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
GCD
蘋果為多核開發(fā)的多線程解決方案茁帽,自動利用CPU內(nèi)核玉罐,自動管理線程的生命周期,使用了C語言和Block潘拨,更加方便靈活的管理多線程吊输。
隊列
串行隊列(連續(xù)性):FIFO(先進先出)串聯(lián)執(zhí)行。包括主隊列dispatch_get_main_queue和自建隊列dispatch_queue_create(第一個參數(shù)表示隊列名铁追,第二個參數(shù)表示隊列類型:DISPATCH_QUEUE_SERIAL或NULL創(chuàng)建串行隊列季蚂,DISPATCH_QUEUE_CONCURRENT創(chuàng)建并行隊列)。
并行隊列(并發(fā)性):全局并行隊列dispatch_get_global_queue(priority指定優(yōu)先級脂信,flag作為保留參數(shù)備用)癣蟋。
注:dispatch_queue_create+DISPATCH_QUEUE_CONCURRENT創(chuàng)建自建并行隊列是沒有必要的,所有并發(fā)操作應(yīng)放在全局并行隊列中以節(jié)省開銷狰闪。
任務(wù)(即一段代碼)
dispatch_sync:創(chuàng)建同步執(zhí)行任務(wù),阻塞當(dāng)前線程直到block結(jié)束濒生,在主線程直接調(diào)用會死鎖:
dispatch_sync(any queue, ^{ // 死鎖 });
在其他串行線程中埋泵,創(chuàng)建同步任務(wù)也會死鎖:
dispatch_sync(create_queue, ^{
? ? ? ? // 當(dāng)前調(diào)用棧向其他隊列(自建隊列)提交block是可以執(zhí)行的
? ? ? ? dispatch_sync(dispatch_get_main_queue(), ^{??// 死鎖? });
});
dispatch_async:創(chuàng)建異步執(zhí)行任務(wù),不阻塞當(dāng)前線程(或者說新開了線程執(zhí)行任務(wù))。
參數(shù):一個隊列丽声,一個block礁蔗,block會在指定的隊列里按照其串行或并行屬性執(zhí)行。
用法和實例
1雁社、不阻塞當(dāng)前線程的情況下浴井,在主隊列中強行插入串行任務(wù)
dispatch_async(dispatch_get_main_queue(), ^{ });
注:如sdwebimage下載圖片時,processblock回調(diào)中的UI更新操作應(yīng)插入主線程霉撵,否則不能實時更新UI磺浙。
2、不阻塞當(dāng)前線程的情況下徒坡,在全局隊列中加入并行任務(wù)
dispatch_async(dispatch_get_global_queue(0, 0), ^{ });
3撕氧、異步在自定義隊列中插入串行任務(wù)
dispatch_queue_t urls_queue = dispatch_queue_create(“test.myQueue", NULL);
dispatch_async(urls_queue, ^{ });
dispatch_release(urls_queue); //釋放隊列(提前結(jié)束線程)
4、隊列組
dispatch_group_t?group?=?dispatch_group_create(); ?//創(chuàng)建隊列組
dispatch_queue_t?queue?=?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0); ?//全局并行隊列
dispatch_group_async(group,?queue,?^{ ?//任務(wù)一 ?});
dispatch_group_async(group,?dispatch_get_main_queue(),?^{ ?//任務(wù)二 ?});
dispatch_group_enter(group); //標(biāo)志隊列組內(nèi)的異步任務(wù)開始喇完,類似引用計數(shù)+1
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
? ? ? //任務(wù)三(任務(wù)中可嵌套處理異步操作伦泥,即處理異步任務(wù)的同步)
? ? ?sleep(5); ?//異步操作
? ? ?dispatch_group_leave(group); ?//標(biāo)志異步任務(wù)結(jié)束,一般寫在異步操作完成的block內(nèi)實現(xiàn)隊列組內(nèi)任務(wù)完成的統(tǒng)一通知
});
dispatch_group_notify(group,?dispatch_get_main_queue(),?^{ ? //當(dāng)group組中的任務(wù)都完成后锦溪,會自動通知 ? });
dispatch_async(dispatch_get_global_queue(0, 0), ^{
? ? dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));? //監(jiān)聽group隊列組中的全部任務(wù)并設(shè)置超時時間
? ? //此處執(zhí)行和dispatch_group_notify的block參數(shù)中一樣的內(nèi)容
});
注:上述三個任務(wù)(全局隊列+主隊列+全局隊列中的異步任務(wù))執(zhí)行順序嚴(yán)格上來說是完全并行無順序的不脯,但實際會按照三個任務(wù)的執(zhí)行順序打印,任務(wù)內(nèi)的每行代碼才會穿插并行刻诊。
5防楷、其他用法
//生命周期內(nèi)的一次性執(zhí)行(單例模式)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ ?});
//延遲執(zhí)行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){ ?});
GCD源碼分析
15+1個隊列
4-15這12個隊列可以用GCD的各種方法獲取,1是main-thread主線程管理池
dispatch_queue_create
執(zhí)行_dispatch_lane_create_with_target(const char* label, dispatch_queue_attr_t? dqa, dispatch_queue_t tq, bool legacy) 方法坏逢,步驟如下:
1域帐、_dispatch_queue_attr_to_info:解析attr生成dqai(如果是串行隊列直接返回{}),并賦值qos(優(yōu)先級)是整、overcommit(沒有空余線程時是否開新的肖揣,串行默認(rèn)true)、concurrent(區(qū)分并行和串行)等浮入。
2龙优、_dispatch_root_queues:創(chuàng)建target隊列——根據(jù)qos和overcommit,從root中拿一個(由于qos*6和overcommit的組合事秀,root queue一共有12個)
3彤断、_dispatch_object_alloc,_dispatch_queue_init:創(chuàng)建隊列易迹,legacy釋放相關(guān)宰衙,根據(jù)dqai中的concurrent設(shè)置DISPATCH_VTABLE類對象,調(diào)用_dispatch_object_alloc(vtable睹欲,sizeof(struct?dispatch_lane_s))申請隊列的內(nèi)存空間供炼,_dispatch_queue_init初始化queue設(shè)置最大并發(fā)數(shù)和激活狀態(tài)一屋。
_dispatch_queue_init的三個參數(shù):_dispatch_object_alloc生成的dispatch_lane_t、legacy轉(zhuǎn)來的dispatch_queue_flags_t袋哼、串行并行決定的的width最大線程數(shù)冀墨、dqai.dqai_inactive決定initial_state_bits激活狀態(tài)。
4涛贯、dq -> dq_label诽嘉,dq -> dq_priority,_dispatch_retain弟翘,dq->do_targetq虫腋,設(shè)置各種屬性
5、return _dispatch_trace_queue_create(dq)._dq; 返回最終創(chuàng)建的隊列衅胀。
dispatch_get_main_queue
return?DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t,_dispatch_main_q);
dispatch_queue_main_t:主隊列類型岔乔,define了
1、_dispatch_get_default_queue(overcommit)為true
2滚躯、root queue為_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_QOS + \ !!(overcommit)]._as_dq雏门。(由此可知是_dispatch_root_queues中的第7個,和串行隊列一致)
_dispatch_main_q:初始化dispatch_queue_static_s結(jié)構(gòu)體掸掏,并賦值state茁影、label、atomic_flags丧凤、serialnum等募闲。
dispatch_get_global_queue
return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT);
獲取方式和結(jié)構(gòu)與主隊列略有區(qū)別,但最終也是去root queue中獲取愿待,根據(jù)qos和overcommit = 0拿到第6個queue浩螺。
create自建隊列 和 main/global queue 獲取方式的區(qū)別
create需要自己alloc和init,最后從root queue里拿一個系統(tǒng)queue作為target queue仍侥。
main和global是根據(jù)qos和overcommit直接拿出對應(yīng)的queue要出。
NSOperation和NSOperationQueue
對GCD的封裝,完全面向?qū)ο笈┰āSOperation對應(yīng)GCD的任務(wù)患蹂;NSOperationQueue對應(yīng)GCD的隊列。將任務(wù)添加到隊列中砸紊,系統(tǒng)自動執(zhí)行传于。
NSOperation
內(nèi)部任務(wù)執(zhí)行狀態(tài)機:ready→executing→finished/cancelled。
NSOperation是個抽象類不能直接使用醉顽,一般使用它的子類NSBlockOperation(用block傳遞任務(wù))和NSInvocationOperation(用@selector傳遞任務(wù))沼溜。
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ ?}];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
[operation start]; //默認(rèn)asynchronous = NO,阻塞當(dāng)前線程
NSBlockOperation有個addExecutionBlock的方法游添,可以給一個任務(wù)添加多個block且在多個線程中并發(fā)執(zhí)行盛末,add一定要在start前弹惦。
注:當(dāng)我們在自定義operation中構(gòu)建異步任務(wù)時(自定義同步任務(wù)無意義因為可以直接用它的兩個子類)否淤,應(yīng)重寫asynchronous屬性(默認(rèn)是NO時任務(wù)執(zhí)行完operation狀態(tài)自動變成finished)的getter返回YES悄但,在異步任務(wù)完成的block中手動設(shè)置finished狀態(tài)(此操作涉及KVO的手動觸發(fā))。重寫main方法時一定要加入@autoreleasepool自動釋放池石抡,因為無法訪問主線程的自動釋放池檐嚣。如果要完全控制狀態(tài)機,也要重寫start方法判斷或者手動觸發(fā)任務(wù)執(zhí)行狀態(tài)的KVO(cancelled啰扛,executing等)嚎京。同GCD中的dispatch_group_enter/leave。
注2:NSInvocation用于主動調(diào)用對象的方法隐解,處理performSelector無法處理的多參數(shù)或有返回值的方法調(diào)用鞍帝。
注3:addDependency可以添加依賴讓NSOperation在隊列中按順序串行,相互依賴會死鎖煞茫。
NSOperationQueue
NSOperation的直接執(zhí)行還是會占用當(dāng)前線程帕涌,所以應(yīng)把任務(wù)加到隊列中,添加完成后续徽,任務(wù)會自動start蚓曼,并根據(jù)NSOperationQueue的maxConcurrentOperationCount屬性決定并行數(shù)(= 1時即為串行),并根據(jù)waitUntilFinished決定是否阻塞當(dāng)前線程(同步異步)钦扭。
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; ?//創(chuàng)建主隊列
NSOperationQueue *otherQueue = [[NSOperationQueue alloc] init]; ?//創(chuàng)建其他隊列
[mainQueue addOperation:operation]; //傳入NSOperation任務(wù)對象
[otherQueue addOperationWithBlock:^{ ?//傳入任務(wù)block }];
[operation2 addDependency:operation1]; //設(shè)置依賴纫版,按順序執(zhí)行任務(wù)
[otherQueue addOperations:@[operation1, operation2] waitUntilFinished:NO]; ?//不阻塞當(dāng)前線程
注:主隊列是在主線程中執(zhí)行的,所以默認(rèn)最大并發(fā)數(shù)就是1客情,且設(shè)置無效其弊。
注2:監(jiān)聽隊列的完成需要手動添加KVO監(jiān)聽operationCount。
其他方法和屬性
@property (getter=isSuspended) BOOL suspended; ?//暫停和繼續(xù)隊列
- (void)cancelAllOperations; ?//取消隊列所有任務(wù)
- (void)waitUntilAllOperationsAreFinished; ?//阻塞線程直至隊列任務(wù)全部完成