不可不說(shuō)的多線程

基本概念

線程:一個(gè)應(yīng)用的運(yùn)行是一個(gè)進(jìn)程,一個(gè)進(jìn)程中可以開(kāi)啟多條線程執(zhí)行不同的任務(wù);線程是CPU分配和調(diào)度資源的最小單位;多線程并發(fā)其實(shí)是GPU的快速切換調(diào)度處理的假象 / 但并行是充分利用多核在多個(gè)線程上同步進(jìn)行;可以提高CPU利用率和運(yùn)行效率,但是過(guò)多時(shí)會(huì)占用內(nèi)存空間,降低性能。

任務(wù):狹義上指的閉包內(nèi)的代碼(如GCD的block中),分sync和async。

隊(duì)列:存放待執(zhí)行任務(wù)的等待隊(duì)列,F(xiàn)IFO原則,分串行和并行。

線程同步:加鎖;串行隊(duì)列;NSOperation的最大并發(fā)數(shù)=1;各種Lock;信號(hào)量;CGD的barrierAPI。

多線程的優(yōu)缺點(diǎn):提升效率,充分利用多核特性;但創(chuàng)建線程需要資源消耗(子線程內(nèi)存占用約512kb);切換線程需要消耗CPU。


GCD

原理:時(shí)間片的輪換。讓任務(wù)在線程中排隊(duì),根據(jù)可用資源安排內(nèi)核處理,底層也是用線程實(shí)現(xiàn),但是自動(dòng)管理線程的聲明周期,不用關(guān)心線程具體的使用情況。

源碼中的隊(duì)列

1個(gè)主線程管理池+1個(gè)其他線程管理池+14個(gè)runloop隊(duì)列。其中12個(gè)可以通過(guò)各種API獲?。篶reate(優(yōu)先級(jí),overcommit,串行并行)組合獲取。

根據(jù)qos,overcommit的組合,root queue一共12個(gè)。

主線程和主隊(duì)列

主隊(duì)列任務(wù)一定在主線程執(zhí)行,但為了線程切換造成的性能消耗,主線程有空時(shí)可能會(huì)執(zhí)行其他隊(duì)列的任務(wù)(CPU的寄存器)。注:在GCD中我們永遠(yuǎn)無(wú)法直接接觸到線程,而是根據(jù)隊(duì)列和任務(wù)的組合選用,讓系統(tǒng)自動(dòng)對(duì)線程進(jìn)行管理調(diào)度。

常用API

dispatch_queue_create創(chuàng)建隊(duì)列(SERIAL串行,CONCURRENT并行)

dispatch_get_main_queue()獲取主隊(duì)列

dispatch_get_global_queue全局隊(duì)列(入?yún)os優(yōu)先級(jí)+overcommit是否過(guò)量開(kāi)線程)

dispatch_after延遲把任務(wù)加入某隊(duì)列

dispatch_barrier_async柵欄方法控制(自定義并行隊(duì)列中分割任務(wù)執(zhí)行順序)

dispatch_once創(chuàng)建單例(保證程序中只執(zhí)行一次)

dispatch_semaphore_create / signal / wait創(chuàng)建信號(hào)量(顆粒度更細(xì)的任務(wù)順序控制,即加鎖原理)

注1:overcommit,串行默認(rèn)true表示內(nèi)存不足也得開(kāi)線程完成串行任務(wù),并行false。

注2:barrier_sync和barrier_async都是等前面的執(zhí)行完再執(zhí)行自己的任務(wù),再執(zhí)行后面的任務(wù)。區(qū)別就是是否等待自己的任務(wù)執(zhí)行完,再把后面的任務(wù)加入隊(duì)列(但都不會(huì)執(zhí)行)。

dispatch_once

多用于創(chuàng)建單例和方法交換。

單例原理:dispatch_once_t是個(gè)整型,外部初始化標(biāo)記,dispatch_once(&onceToken)在初始化的標(biāo)記下,才會(huì)執(zhí)行Block,執(zhí)行后標(biāo)記掉dispatch_atomic_cmpxchg { block; dispatch_atomic_barrier內(nèi)存屏障,加鎖原子操作賦值onceToken }(所以要傳地址),barrier實(shí)現(xiàn)原子操作保證線程安全,標(biāo)記之后再次進(jìn)入會(huì)調(diào)用_dispatch_hardware_pause以節(jié)省CPU。

注意事項(xiàng):?jiǎn)卫腷lock調(diào)用期間,多次請(qǐng)求同類(lèi)的dispatch_once會(huì)造成請(qǐng)求鏈表無(wú)限增長(zhǎng),造成死鎖。

單例的弊端:濫用單例會(huì)浪費(fèi)資源占用內(nèi)存;沒(méi)有抽象層接口,難以擴(kuò)展;職責(zé)過(guò)重未被單一職責(zé);可能被回收造成狀態(tài)丟失。


死鎖

核心原因:任務(wù)的相互等待,如單例多次調(diào)用,串行隊(duì)列追加同步任務(wù)。

具體原因:首先得是個(gè)同步任務(wù)操作(即sync一個(gè)任務(wù)),然后要sync到一個(gè)當(dāng)前隊(duì)列(別的隊(duì)列肯定不阻塞啦),最后這個(gè)隊(duì)列要串行的(并行的當(dāng)然不阻塞啦),典型的就是主線程sync。

總結(jié):當(dāng)前隊(duì)列(串行)追加同步任務(wù)(sync)。

注1:由于主隊(duì)列的任務(wù)一定在主線程上執(zhí)行,所以在主線程往主隊(duì)列追加任務(wù),就是往主線程同步任務(wù),會(huì)阻塞主線程,造成死鎖。

注2:死鎖其實(shí)是隊(duì)列任務(wù)互相等待引起的,而與線程無(wú)關(guān)。比如自建兩個(gè)隊(duì)列,嵌套sync,雖然是在同一線程執(zhí)行(因?yàn)槭莝ync),但因?yàn)殛?duì)列不同所以不會(huì)造成死鎖。


其他多線程方案

pthread:C語(yǔ)言跨平臺(tái)的多線程API

NSThread:面向?qū)ο蟮妮p量級(jí)多線程方案,手動(dòng)管理生命周期,適合簡(jiǎn)單的場(chǎng)景。

NSOpertaion+NSOperationQueue:對(duì)GCD的封裝,面向?qū)ο?,可設(shè)置跨隊(duì)列的依賴(lài)關(guān)系。


線程同步

為了防止多個(gè)線程搶奪同一個(gè)資源造成的數(shù)據(jù)安全問(wèn)題,給線程加鎖的操作。

原子操作:atomic修飾符-getter、setter加鎖。

OSMemoryBarrier:內(nèi)存屏障確保書(shū)寫(xiě)順序。

Volatile:修飾變量,告訴編譯器變量從內(nèi)存而非寄存器讀取。

其他加鎖:OSSpinLock自旋鎖,pthread_mutex互斥鎖,@synchronize同步鎖,dispatch_semaphore信號(hào)量加鎖。


同步執(zhí)行

GCD:將操作放入自建隊(duì)列(串行)中。

NSOperation:任務(wù)放入自建隊(duì)列并將最大并發(fā)數(shù)設(shè)置為1,設(shè)置跨隊(duì)列依賴(lài)addDependency。

barrier_sync:柵欄方法,并發(fā)隊(duì)列中先執(zhí)行之前的。

semphore:信號(hào)量,收到signal+1,執(zhí)行wait-1,<=0等待。



------舊版------

關(guān)鍵字:多線程原理,線程(偏CPU),隊(duì)列(串行并行),任務(wù)(同步異步),GCD及其源碼分析,@synchronized

概述

一個(gè)應(yīng)用的運(yùn)行是一個(gè)進(jìn)程,一個(gè)進(jìn)程中可以開(kāi)啟多條線程用于執(zhí)行不同的任務(wù),提高程序執(zhí)行效率,但線程過(guò)多會(huì)占用大量?jī)?nèi)存空間,降低性能。iOS中一般將UI事件的處理放在主線程里。一些耗時(shí)的操作不應(yīng)放入主線程,應(yīng)新開(kāi)線程異步執(zhí)行。

原理:CPU只能處理一條線程,多線程實(shí)際上是CPU快速在多條線程間不斷切換調(diào)度,而切換調(diào)度的時(shí)間特別快,造成了并發(fā)處理的假象。

隊(duì)列與線程:隊(duì)列是對(duì)線程的包裝,便于使用,偏程序(線程偏CPU)。隊(duì)列的底層也是通過(guò)線程實(shí)現(xiàn)的。

任務(wù)(狹義的閉包含義,非進(jìn)程層級(jí)的廣義任務(wù)):根據(jù)是否開(kāi)辟新線程,任務(wù)分為同步和異步,區(qū)別為是否阻塞當(dāng)前線程。

iOS中的多線程

pthread

一套C語(yǔ)言編寫(xiě)的通用的跨平臺(tái)的多線程API,iOS中不常用。忽略。


NSThread

面向?qū)ο蟮妮p量級(jí)的多線程方案,更直觀的控制線程對(duì)象,需手動(dòng)管理生命周期,但多適用于比較簡(jiǎn)單的場(chǎng)景。

創(chuàng)建

1、實(shí)例方法創(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、類(lèi)方法創(chuàng)建(需手動(dòng)start,可在線程開(kāi)始前配置stack大小和優(yōu)先級(jí))

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

蘋(píng)果為多核開(kāi)發(fā)的多線程解決方案,自動(dòng)利用CPU內(nèi)核,自動(dòng)管理線程的生命周期,使用了C語(yǔ)言和Block,更加方便靈活的管理多線程。

隊(duì)列

串行隊(duì)列(連續(xù)性):FIFO(先進(jìn)先出)串聯(lián)執(zhí)行。包括主隊(duì)列dispatch_get_main_queue和自建隊(duì)列dispatch_queue_create(第一個(gè)參數(shù)表示隊(duì)列名,第二個(gè)參數(shù)表示隊(duì)列類(lèi)型:DISPATCH_QUEUE_SERIAL或NULL創(chuàng)建串行隊(duì)列,DISPATCH_QUEUE_CONCURRENT創(chuàng)建并行隊(duì)列)。

并行隊(duì)列(并發(fā)性):全局并行隊(duì)列dispatch_get_global_queue(priority指定優(yōu)先級(jí),flag作為保留參數(shù)備用)。

注:dispatch_queue_create+DISPATCH_QUEUE_CONCURRENT創(chuàng)建自建并行隊(duì)列是沒(méi)有必要的,所有并發(fā)操作應(yīng)放在全局并行隊(duì)列中以節(jié)省開(kāi)銷(xiāo)。

任務(wù)(即一段代碼)

dispatch_sync:創(chuàng)建同步執(zhí)行任務(wù),阻塞當(dāng)前線程直到block結(jié)束,在主線程直接調(diào)用會(huì)死鎖:

dispatch_sync(any queue, ^{ // 死鎖 });

在其他串行線程中,創(chuàng)建同步任務(wù)也會(huì)死鎖:

dispatch_sync(create_queue, ^{

? ? ? ? // 當(dāng)前調(diào)用棧向其他隊(duì)列(自建隊(duì)列)提交block是可以執(zhí)行的

? ? ? ? dispatch_sync(dispatch_get_main_queue(), ^{??// 死鎖? });

});

dispatch_async:創(chuàng)建異步執(zhí)行任務(wù),不阻塞當(dāng)前線程(或者說(shuō)新開(kāi)了線程執(zhí)行任務(wù))。

參數(shù):一個(gè)隊(duì)列,一個(gè)block,block會(huì)在指定的隊(duì)列里按照其串行或并行屬性執(zhí)行。

用法和實(shí)例

1、不阻塞當(dāng)前線程的情況下,在主隊(duì)列中強(qiáng)行插入串行任務(wù)

dispatch_async(dispatch_get_main_queue(), ^{ });

注:如sdwebimage下載圖片時(shí),processblock回調(diào)中的UI更新操作應(yīng)插入主線程,否則不能實(shí)時(shí)更新UI。

2、不阻塞當(dāng)前線程的情況下,在全局隊(duì)列中加入并行任務(wù)

dispatch_async(dispatch_get_global_queue(0, 0), ^{ });

3、異步在自定義隊(duì)列中插入串行任務(wù)

dispatch_queue_t urls_queue = dispatch_queue_create(“test.myQueue", NULL);

dispatch_async(urls_queue, ^{ });

dispatch_release(urls_queue); //釋放隊(duì)列(提前結(jié)束線程)

4、隊(duì)列組

dispatch_group_t?group?=?dispatch_group_create(); ?//創(chuàng)建隊(duì)列組

dispatch_queue_t?queue?=?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0); ?//全局并行隊(duì)列

dispatch_group_async(group,?queue,?^{ ?//任務(wù)一 ?});

dispatch_group_async(group,?dispatch_get_main_queue(),?^{ ?//任務(wù)二 ?});

dispatch_group_enter(group); //標(biāo)志隊(duì)列組內(nèi)的異步任務(wù)開(kāi)始,類(lèi)似引用計(jì)數(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é)束,一般寫(xiě)在異步操作完成的block內(nèi)實(shí)現(xiàn)隊(duì)列組內(nèi)任務(wù)完成的統(tǒng)一通知

});

dispatch_group_notify(group,?dispatch_get_main_queue(),?^{ ? //當(dāng)group組中的任務(wù)都完成后,會(huì)自動(dòng)通知 ? });

dispatch_async(dispatch_get_global_queue(0, 0), ^{

? ? dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));? //監(jiān)聽(tīng)group隊(duì)列組中的全部任務(wù)并設(shè)置超時(shí)時(shí)間

? ? //此處執(zhí)行和dispatch_group_notify的block參數(shù)中一樣的內(nèi)容

});

注:上述三個(gè)任務(wù)(全局隊(duì)列+主隊(duì)列+全局隊(duì)列中的異步任務(wù))執(zhí)行順序嚴(yán)格上來(lái)說(shuō)是完全并行無(wú)順序的,但實(shí)際會(huì)按照三個(gè)任務(wù)的執(zhí)行順序打印,任務(wù)內(nèi)的每行代碼才會(huì)穿插并行。

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個(gè)隊(duì)列

4-15這12個(gè)隊(duì)列可以用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(如果是串行隊(duì)列直接返回{}),并賦值qos(優(yōu)先級(jí))、overcommit(沒(méi)有空余線程時(shí)是否開(kāi)新的,串行默認(rèn)true)、concurrent(區(qū)分并行和串行)等。

2、_dispatch_root_queues:創(chuàng)建target隊(duì)列——根據(jù)qos和overcommit,從root中拿一個(gè)(由于qos*6和overcommit的組合,root queue一共有12個(gè))

3、_dispatch_object_alloc,_dispatch_queue_init:創(chuàng)建隊(duì)列,legacy釋放相關(guān),根據(jù)dqai中的concurrent設(shè)置DISPATCH_VTABLE類(lèi)對(duì)象,調(diào)用_dispatch_object_alloc(vtable,sizeof(struct?dispatch_lane_s))申請(qǐng)隊(duì)列的內(nèi)存空間,_dispatch_queue_init初始化queue設(shè)置最大并發(fā)數(shù)和激活狀態(tài)。

_dispatch_queue_init的三個(gè)參數(shù):_dispatch_object_alloc生成的dispatch_lane_t、legacy轉(zhuǎn)來(lái)的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)建的隊(duì)列。

dispatch_get_main_queue

return?DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t,_dispatch_main_q);

dispatch_queue_main_t:主隊(duì)列類(lèi)型,define了

1、_dispatch_get_default_queue(overcommit)為true

2、root queue為_(kāi)dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_QOS + \ !!(overcommit)]._as_dq。(由此可知是_dispatch_root_queues中的第7個(gè),和串行隊(duì)列一致)

_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)與主隊(duì)列略有區(qū)別,但最終也是去root queue中獲取,根據(jù)qos和overcommit = 0拿到第6個(gè)queue。

create自建隊(duì)列 和 main/global queue 獲取方式的區(qū)別

create需要自己alloc和init,最后從root queue里拿一個(gè)系統(tǒng)queue作為target queue。

main和global是根據(jù)qos和overcommit直接拿出對(duì)應(yīng)的queue。


NSOperation和NSOperationQueue

對(duì)GCD的封裝,完全面向?qū)ο?。NSOperation對(duì)應(yīng)GCD的任務(wù);NSOperationQueue對(duì)應(yīng)GCD的隊(duì)列。將任務(wù)添加到隊(duì)列中,系統(tǒng)自動(dòng)執(zhí)行。

NSOperation

內(nèi)部任務(wù)執(zhí)行狀態(tài)機(jī):ready→executing→finished/cancelled。

NSOperation是個(gè)抽象類(lèi)不能直接使用,一般使用它的子類(lèi)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有個(gè)addExecutionBlock的方法,可以給一個(gè)任務(wù)添加多個(gè)block且在多個(gè)線程中并發(fā)執(zhí)行,add一定要在start前。

注:當(dāng)我們?cè)?b>自定義operation中構(gòu)建異步任務(wù)時(shí)(自定義同步任務(wù)無(wú)意義因?yàn)榭梢灾苯佑盟膬蓚€(gè)子類(lèi)),應(yīng)重寫(xiě)asynchronous屬性(默認(rèn)是NO時(shí)任務(wù)執(zhí)行完operation狀態(tài)自動(dòng)變成finished)的getter返回YES,在異步任務(wù)完成的block中手動(dòng)設(shè)置finished狀態(tài)(此操作涉及KVO的手動(dòng)觸發(fā))。重寫(xiě)main方法時(shí)一定要加入@autoreleasepool自動(dòng)釋放池,因?yàn)闊o(wú)法訪問(wèn)主線程的自動(dòng)釋放池。如果要完全控制狀態(tài)機(jī),也要重寫(xiě)start方法判斷或者手動(dòng)觸發(fā)任務(wù)執(zhí)行狀態(tài)的KVO(cancelled,executing等)。同GCD中的dispatch_group_enter/leave。

注2:NSInvocation用于主動(dòng)調(diào)用對(duì)象的方法,處理performSelector無(wú)法處理的多參數(shù)或有返回值的方法調(diào)用。

注3:addDependency可以添加依賴(lài)讓NSOperation在隊(duì)列中按順序串行,相互依賴(lài)會(huì)死鎖。

NSOperationQueue

NSOperation的直接執(zhí)行還是會(huì)占用當(dāng)前線程,所以應(yīng)把任務(wù)加到隊(duì)列中,添加完成后,任務(wù)會(huì)自動(dòng)start,并根據(jù)NSOperationQueue的maxConcurrentOperationCount屬性決定并行數(shù)(= 1時(shí)即為串行),并根據(jù)waitUntilFinished決定是否阻塞當(dāng)前線程(同步異步)。

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; ?//創(chuàng)建主隊(duì)列

NSOperationQueue *otherQueue = [[NSOperationQueue alloc] init]; ?//創(chuàng)建其他隊(duì)列

[mainQueue addOperation:operation]; //傳入NSOperation任務(wù)對(duì)象

[otherQueue addOperationWithBlock:^{ ?//傳入任務(wù)block }];

[operation2 addDependency:operation1]; //設(shè)置依賴(lài),按順序執(zhí)行任務(wù)

[otherQueue addOperations:@[operation1, operation2] waitUntilFinished:NO]; ?//不阻塞當(dāng)前線程

注:主隊(duì)列是在主線程中執(zhí)行的,所以默認(rèn)最大并發(fā)數(shù)就是1,且設(shè)置無(wú)效。

注2:監(jiān)聽(tīng)隊(duì)列的完成需要手動(dòng)添加KVO監(jiān)聽(tīng)operationCount。

其他方法和屬性

@property (getter=isSuspended) BOOL suspended; ?//暫停和繼續(xù)隊(duì)列

- (void)cancelAllOperations; ?//取消隊(duì)列所有任務(wù)

- (void)waitUntilAllOperationsAreFinished; ?//阻塞線程直至隊(duì)列任務(wù)全部完成

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容