什么是線程
1、線程的定義、狀態(tài)、屬性
進(jìn)程
進(jìn)程:(Process)是計(jì)算機(jī)中的程序關(guān)于某數(shù)據(jù)集合上的一次運(yùn)行活動(dòng),是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)。
在早期面向進(jìn)程設(shè)計(jì)的計(jì)算機(jī)結(jié)構(gòu)中,進(jìn)程是程序的基本執(zhí)行實(shí)體;
在當(dāng)代面向線程設(shè)計(jì)的計(jì)算機(jī)結(jié)構(gòu)中,進(jìn)程是線程的容器。程序是指令、數(shù)據(jù)及其組織形式的描述,進(jìn)程是程序的實(shí)體。
每個(gè)進(jìn)程都有獨(dú)立的代碼和數(shù)據(jù)空間(進(jìn)程上下文),進(jìn)程間的切換會(huì)有較大的開銷,一個(gè)進(jìn)程包含1--n個(gè)線程。(進(jìn)程是資源分配的最小單位)
線程
線程:有時(shí)被稱為輕量級(jí)進(jìn)程(Lightweight Process,LWP),是程序執(zhí)行流的最小單元。
一個(gè)標(biāo)準(zhǔn)的線程由線程ID,當(dāng)前指令指針(PC),寄存器集合和堆棧組成。
另外,線程是進(jìn)程中的一個(gè)實(shí)體,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源,只擁有一點(diǎn)兒在運(yùn)行中必不可少的資源,但它可與同屬一個(gè)進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源。
一個(gè)線程可以創(chuàng)建和撤消另一個(gè)線程,同一進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行。由于線程之間的相互制約,致使線程在運(yùn)行中呈現(xiàn)出間斷性。線程也有就緒、阻塞和運(yùn)行三種基本狀態(tài)。
就緒狀態(tài)是指線程具備運(yùn)行的所有條件,邏輯上可以運(yùn)行,在等待處理機(jī);
運(yùn)行狀態(tài)是指線程占有處理機(jī)正在運(yùn)行;
阻塞狀態(tài)是指線程在等待一個(gè)事件(如某個(gè)信號(hào)量),邏輯上不可執(zhí)行。
每一個(gè)程序都至少有一個(gè)線程,若程序只有一個(gè)線程,那就是程序本身。
多線程:線程是程序中一個(gè)單一的順序控制流程。進(jìn)程內(nèi)一個(gè)相對(duì)獨(dú)立的、可調(diào)度的執(zhí)行單元,是系統(tǒng)獨(dú)立調(diào)度和分派CPU的基本單位指運(yùn)行中的程序的調(diào)度單位。在單個(gè)程序中同時(shí)運(yùn)行多個(gè)線程完成不同的工作,稱為多線程。
同一類線程共享代碼和數(shù)據(jù)空間,每個(gè)線程有獨(dú)立的運(yùn)行棧和程序計(jì)數(shù)器(PC),線程切換開銷小。(線程是cpu調(diào)度的最小單位)
多進(jìn)程是指操作系統(tǒng)能同時(shí)運(yùn)行多個(gè)任務(wù)(程序)。
多線程是指在同一程序中有多個(gè)順序流在執(zhí)行。
線程與進(jìn)程的共同點(diǎn)和區(qū)別
共同點(diǎn):線程和進(jìn)程一樣分為五個(gè)階段:創(chuàng)建、就緒、運(yùn)行、阻塞、終止。
區(qū)別:
線程和進(jìn)程的區(qū)別在于,子進(jìn)程和父進(jìn)程有不同的代碼和數(shù)據(jù)空間,而多個(gè)線程則共享數(shù)據(jù)空間,每個(gè)線程有自己的執(zhí)行堆棧和程序計(jì)數(shù)器為其執(zhí)行上下文。多線程主要是為了節(jié)約CPU時(shí)間,發(fā)揮利用,根據(jù)具體情況而定。線程的運(yùn)行中需要使用計(jì)算機(jī)的內(nèi)存資源和CPU。
線程與進(jìn)程的區(qū)別可以歸納為以下幾點(diǎn):
1)地址空間和其它資源(如打開文件):進(jìn)程間相互獨(dú)立,同一進(jìn)程的各線程間共享。某進(jìn)程內(nèi)的線程在其它進(jìn)程不可見。
2)通信:進(jìn)程間通信IPC,線程間可以直接讀寫進(jìn)程數(shù)據(jù)段(如全局變量)來(lái)進(jìn)行通信——需要進(jìn)程同步和互斥手段的輔助,以保證數(shù)據(jù)的一致性。
3)調(diào)度和切換:線程上下文切換比進(jìn)程上下文切換要快得多。
4)在多線程OS中,進(jìn)程不是一個(gè)可執(zhí)行的實(shí)體。
線程的狀態(tài)
就緒:線程分配了CPU以外的全部資源,等待獲得CPU調(diào)度
執(zhí)行:線程獲得CPU,正在執(zhí)行
阻塞:線程由于發(fā)生I/O或者其他的操作導(dǎo)致無(wú)法繼續(xù)執(zhí)行,就放棄處理機(jī),轉(zhuǎn)入線程就緒
線程的特性
線程在多線程OS中,通常是在一個(gè)進(jìn)程中包括多個(gè)線程,每個(gè)線程都是作為利用CPU的基本單位,是花費(fèi)最小開銷的實(shí)體。線程具有以下屬性。
①輕型實(shí)體
線程中的實(shí)體基本上不擁有系統(tǒng)資源,只是有一點(diǎn)必不可少的、能保證獨(dú)立運(yùn)行的資源,比如,在每個(gè)線程中都應(yīng)具有一個(gè)用于控制線程運(yùn)行的線程控制塊TCB,用于指示被執(zhí)行指令序列的程序計(jì)數(shù)器、保留局部變量、少數(shù)狀態(tài)參數(shù)和返回地址等的一組寄存器和堆棧。
②獨(dú)立調(diào)度和分派的基本單位。
在多線程OS中,線程是能獨(dú)立運(yùn)行的基本單位,因而也是獨(dú)立調(diào)度和分派的基本單位。由于線程很“輕”,故線程的切換非常迅速且開銷小。
③可并發(fā)執(zhí)行。
在一個(gè)進(jìn)程中的多個(gè)線程之間,可以并發(fā)執(zhí)行,甚至允許在一個(gè)進(jìn)程中所有線程都能并發(fā)執(zhí)行;同樣,不同進(jìn)程中的線程也能并發(fā)執(zhí)行。
④共享進(jìn)程資源。
在同一進(jìn)程中的各個(gè)線程,都可以共享該進(jìn)程所擁有的資源,這首先表現(xiàn)在:所有線程都具有相同的地址空間(進(jìn)程的地址空間),這意味著,線程可以訪問(wèn)該地址空間的每一個(gè)虛地址;此外,還可以訪問(wèn)進(jìn)程所擁有的已打開文件、定時(shí)器、信號(hào)量機(jī)構(gòu)等。
2、線程之間的通信
什么是線程通信
多個(gè)線程在處理同一個(gè)資源,并且任務(wù)不同時(shí),需要線程通信來(lái)幫助解決線程之間對(duì)同一個(gè)變量的使用或操作。就是多個(gè)線程在操作同一份數(shù)據(jù)時(shí), 避免對(duì)同一共享變量的爭(zhēng)奪。
就是在一個(gè)線程進(jìn)行了規(guī)定操作后,就進(jìn)入等待狀態(tài)(wait), 等待其他線程執(zhí)行完他們的指定代碼過(guò)后 再將其喚醒(notify);
當(dāng)我們創(chuàng)建多個(gè)生產(chǎn)者和消費(fèi)者時(shí),無(wú)法直到到底要喚醒哪一個(gè),所以這時(shí)候我們就用到了notifAll()方法。
為什么要線程通信
多個(gè)線程并發(fā)執(zhí)行時(shí), 在默認(rèn)情況下CPU是隨機(jī)切換線程的,當(dāng)我們需要多個(gè)線程來(lái)共同完成一件任務(wù),并且我們希望他們有規(guī)律的執(zhí)行, 那么多線程之間需要一些協(xié)調(diào)通信,以此來(lái)幫我們達(dá)到多線程共同操作一份數(shù)據(jù)。
當(dāng)然如果我們沒(méi)有使用線程通信來(lái)使用多線程共同操作同一份數(shù)據(jù)的話,雖然可以實(shí)現(xiàn),但是在很大程度會(huì)造成多線程之間對(duì)同一共享變量的爭(zhēng)奪,那樣的話勢(shì)必為造成很多錯(cuò)誤和損失!
所以,我們才引出了線程之間的通信,多線程之間的通信能夠避免對(duì)同一共享變量的爭(zhēng)奪。
3、線程進(jìn)程以及堆棧關(guān)系的總結(jié)
棧是線程獨(dú)有的,保存其運(yùn)行狀態(tài)和局部自動(dòng)變量的,棧在線程開始的時(shí)候初始化,每個(gè)線程的棧相互對(duì)立,因此,棧是線程安全的,??臻g有系統(tǒng)管理。棧被自動(dòng)分配到進(jìn)程的內(nèi)存空間中。
堆在操作系統(tǒng)度進(jìn)程初始化的時(shí)候分配,運(yùn)行過(guò)程中也可以向系統(tǒng)要額外的堆,但是用完要返還,不然就是內(nèi)存泄露。
iOS中的線程
iOS中提供了四套多線程方案、一種一種來(lái)看。
Pthreads (不做介紹)
NSThread
GCD
NSOperation & NSOperationQueue
1、NSThread
蘋果封裝、面向?qū)ο蟮?、可以直接操控線程對(duì)象,非常直觀和方便。但是,它的生命周期還是需要我們手動(dòng)管理。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):輕量級(jí)
缺點(diǎn):一個(gè)NSThread對(duì)象代表一個(gè)線程,需要手動(dòng)管理線程的生命周期,處理線程同步等問(wèn)題,線程同步對(duì)數(shù)據(jù)的加鎖會(huì)有一定的開銷。
創(chuàng)建并啟動(dòng)
1、先創(chuàng)建線程類,再啟動(dòng)
//1 創(chuàng)建NSThread 并啟動(dòng)
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
[thread start];
2、創(chuàng)建并自動(dòng)啟動(dòng)
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
//////
__weak typeof(self) weakself = self;
[NSThread detachNewThreadWithBlock:^{
[weakself run];
}];
3、使用 NSObject 的方法創(chuàng)建并自動(dòng)啟動(dòng)
[self performSelectorInBackground:@selector(run) withObject:nil];
但是在Swift中沒(méi)有這個(gè)方法:
Note: The performSelector: method and related selector-invoking methods are not imported in Swift because they are inherently unsafe.
//共同執(zhí)行的方法\兩種鎖
- (void)run {
// [lock lock];
// NSLog(@"111111");
// NSLog(@"---%@",NSThread.currentThread);
// [lock unlock];
@synchronized (self) {
NSLog(@"111111%@",NSThread.currentThread);
NSLog(@"---%@",NSThread.currentThread);
}
}
執(zhí)行結(jié)果:
2017-11-09 10:55:34.196040+0800 MultithreadingDemo[96041:6103178] 111111<NSThread: 0x604000273200>{number = 5, name = (null)}
2017-11-09 10:55:34.196457+0800 MultithreadingDemo[96041:6103178] ---<NSThread: 0x604000273200>{number = 5, name = (null)}
2017-11-09 10:55:34.197956+0800 MultithreadingDemo[96041:6103177] 111111<NSThread: 0x604000273100>{number = 3, name = (null)}
2017-11-09 10:55:34.198510+0800 MultithreadingDemo[96041:6103177] ---<NSThread: 0x604000273100>{number = 3, name = (null)}
2017-11-09 10:55:34.200726+0800 MultithreadingDemo[96041:6103179] 111111<NSThread: 0x604000273140>{number = 4, name = (null)}
2017-11-09 10:55:34.201170+0800 MultithreadingDemo[96041:6103179] ---<NSThread: 0x604000273140>{number = 4, name = (null)}
其他方法
除了創(chuàng)建啟動(dòng)外,NSThread 還以很多方法,下面是一些常見的方法
//取消線程
- (void)cancel;
//啟動(dòng)線程
- (void)start;
//判斷某個(gè)線程的狀態(tài)的屬性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
//獲取當(dāng)前線程信息
+ (NSThread *)currentThread;
//獲取主線程信息
+ (NSThread *)mainThread;
//使當(dāng)前線程暫停一段時(shí)間,或者暫停到某個(gè)時(shí)刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;
2、GCD
Grand Central Dispatch,是libdispatch的市場(chǎng)名稱,而libdispatch是Apple的一個(gè)庫(kù),其為并發(fā)代碼在iOS和OS X的多核硬件上執(zhí)行提供支持。確切地說(shuō)GCD是一套低層級(jí)的C API,通過(guò) GCD,開發(fā)者只需要向隊(duì)列中添加一段代碼塊(block或C函數(shù)指針),而不需要直接和線程打交道。GCD在后端管理著一個(gè)線程池,它不僅決定著你的代碼塊將在哪個(gè)線程被執(zhí)行,還根據(jù)可用的系統(tǒng)資源對(duì)這些線程進(jìn)行管理。這樣通過(guò)GCD來(lái)管理線程,從而解決線程生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)問(wèn)題。同時(shí)自動(dòng)合理地利用更多的CPU內(nèi)核(比如雙核、四核)。
GCD 優(yōu)點(diǎn)
易用: GCD 提供一個(gè)易于使用的并發(fā)模型而不僅僅只是鎖和線程,以幫助我們避開并發(fā)陷阱,而且因?yàn)榛赽lock,它能極為簡(jiǎn)單得在不同代碼作用域之間傳遞上下文。
靈活: GCD 具有在常見模式上(比如鎖、單例),用更高性能的方法優(yōu)化代碼,而且GCD能提供更多的控制權(quán)力以及大量的底層函數(shù)。
性能: GCD 能自動(dòng)根據(jù)系統(tǒng)負(fù)載來(lái)增減線程數(shù)量,這就減少了上下文切換以及增加了計(jì)算效率。
GCD 概念
1.Dispatch Object
GCD被組建成面向?qū)ο蟮娘L(fēng)格。GCD對(duì)象被稱為 dispatch object, 所有的 dispatch object 都是OC對(duì)象.,就如其他OC對(duì)象一樣,當(dāng)開啟了 ARC 時(shí),dispatch object 的retain和release都會(huì)自動(dòng)執(zhí)行。而如果是MRC的話,dispatch objects會(huì)使用dispatch_retain和dispatch_release這兩個(gè)方法來(lái)控制引用計(jì)數(shù)。
在 iOS 6.0 dispatch_release 已被廢棄。內(nèi)部被改成對(duì)象釋放(release)所以 arc 后都不再使用
2.Serial & Concurrent
串行任務(wù)就是每次只有一個(gè)任務(wù)被執(zhí)行,并發(fā)任務(wù)就是在同一時(shí)間可以有多個(gè)任務(wù)被執(zhí)行。
3.Synchronous & Asynchronous
Synchronous(同步函數(shù))意思是在完成了它預(yù)定的任務(wù)后才返回,在任務(wù)執(zhí)行時(shí)會(huì)阻塞當(dāng)前線程。而 Asynchronous(異步函數(shù))則是任務(wù)會(huì)完成但不會(huì)等它完成,所以異步函數(shù)不會(huì)阻塞當(dāng)前線程,會(huì)繼續(xù)去執(zhí)行下去。
4.Concurrency & Parallelism
Concurrency (并發(fā))的意思就是同時(shí)運(yùn)行多個(gè)任務(wù)。這些任務(wù)可能是以在單核 CPU 上以分時(shí)(時(shí)間共享)的形式同時(shí)運(yùn)行,也可能是在多核 CPU 上以真正的并行方式來(lái)運(yùn)行。然后為了使單核設(shè)備也能實(shí)現(xiàn)這一點(diǎn),并發(fā)任務(wù)必須先運(yùn)行一個(gè)線程,執(zhí)行一個(gè)上下文切換,然后運(yùn)行另一個(gè)線程或進(jìn)程。Parallelism(并行)則是真正意思上的多任務(wù)同時(shí)運(yùn)行。
5.Context Switch
Context Switch即上下文切換,一個(gè)上下文切換指當(dāng)你在單個(gè)進(jìn)程里切換執(zhí)行不同的線程時(shí)存儲(chǔ)與恢復(fù)執(zhí)行狀態(tài)的過(guò)程。這個(gè)過(guò)程在編寫多任務(wù)應(yīng)用時(shí)很普遍,但會(huì)帶來(lái)一些額外的開銷。
6.Dispatch Queues
GCD dispatch queues 是一個(gè)強(qiáng)大的執(zhí)行多任務(wù)的工具。Dispatch queue 是一個(gè)對(duì)象,它可以接受任務(wù),并將任務(wù)以先進(jìn)先出(FIFO)的順序來(lái)執(zhí)行。Dispatch queue 可以并發(fā)的或串行的執(zhí)行任意一個(gè)代碼塊,而且并發(fā)任務(wù)會(huì)像 NSOperationQueue 那樣基于系統(tǒng)負(fù)載來(lái)合適地并發(fā)進(jìn)行,串行隊(duì)列同一時(shí)間則只執(zhí)行單一任務(wù)。Dispatch queues 內(nèi)部使用的是線程,GCD 管理這些線程,并且使用 Dispatch queues 的時(shí)候,我們都不需要自己創(chuàng)建線程。Dispatch queues相對(duì)于和線程直接通信的代碼優(yōu)勢(shì)是:使用起來(lái)特別方便,執(zhí)行任務(wù)更加有效率。
7.Queue Types
- main queue : 主隊(duì)列 (主線程)
一般使用 main queue, 都是在該線程中操作 UI 相關(guān)的.也就是說(shuō), 在 main queue 中執(zhí)行的任務(wù)會(huì)在主線程中執(zhí)行.主線程只有一個(gè), main queue 是與主線程相關(guān)的,所以 main queue 是串行隊(duì)列.
//Returns the default queue that is bound to the main thread.
dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q);
}
- global queue : 全局隊(duì)列 (有多個(gè)線程)
dispatch_get_global_queue(long identifier, unsigned long flags); dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
常寫作 dispatch_get_global_queue(0, 0);
global queue 是并發(fā)隊(duì)列.可以設(shè)置其優(yōu)先級(jí). ????jī)?yōu)先級(jí)問(wèn)題
//@param identifier 優(yōu)先級(jí)
- A quality of service class defined in qos_class_t or a priority defined in
- dispatch_queue_priority_t.
//@param flags 備用參數(shù)
- Reserved for future use. Passing any value other than zero may result in
- a NULL return value.
//@result 返回一個(gè)全局隊(duì)列
- Returns the requested global queue or NULL if the requested global queue
- does not exist.
- custom queue : 自定義隊(duì)列 (串行:?jiǎn)尉€程 ,并行:有多個(gè)線程)
這些隊(duì)列是可以是串行的, 也可以是并行的。默認(rèn)是串行的.
dispatch_queue_attr_t設(shè)置成NULL的時(shí)候默認(rèn)代表串行。
串行隊(duì)列可以保證任務(wù)是串行的, 保證了執(zhí)行順序.類似鎖機(jī)制.
dispatch_queue_create(const char *_Nullable label,dispatch_queue_attr_t _Nullable attr);
//@param label 隊(duì)列名稱 盡量別重名
- A string label to attach to the queue.
- This parameter is optional and may be NULL.
//@param attr 隊(duì)列類型 默認(rèn) DISPATCH_QUEUE_SERIAL
- A predefined attribute such as DISPATCH_QUEUE_SERIAL,
- DISPATCH_QUEUE_CONCURRENT, or the result of a call to
- a dispatch_queue_attr_make_with_* function.
//@result
- The newly created dispatch queue.
GCD的具體使用
1.添加任務(wù)到隊(duì)列
GCD有兩種方式來(lái)把任務(wù)添加到隊(duì)列中:異步和同步。
異步方式添加任務(wù)到隊(duì)列的情況:
1.自定義串行隊(duì)列:按添加進(jìn)隊(duì)列的先后順序 順序執(zhí)行(不管同步異步線程)
我們接著上面的run方法來(lái)寫一個(gè)串行隊(duì)列
第一步,寫兩個(gè)異步線程和一個(gè)同步線程加入隊(duì)列執(zhí)行:其中第一個(gè)線程執(zhí)行任務(wù)之前睡眠1秒
[self run];
__weak typeof(self) weakself = self;
dispatch_queue_t queue = dispatch_queue_create("串行隊(duì)列", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
sleep(1);
[weakself run];
});
dispatch_async(queue, ^{
[weakself run];
});
dispatch_sync(queue, ^{
[weakself run];
});
結(jié)果:
2017-11-09 14:00:22.642407+0800 MultithreadingDemo[97236:6195716] ---<NSThread: 0x604000069240>{number = 1, name = main}
2017-11-09 14:00:23.643340+0800 MultithreadingDemo[97236:6195829] ---<NSThread: 0x604000275300>{number = 3, name = (null)}
2017-11-09 14:00:23.643547+0800 MultithreadingDemo[97236:6195829] ---<NSThread: 0x604000275300>{number = 3, name = (null)}
2017-11-09 14:00:23.643776+0800 MultithreadingDemo[97236:6195716] ---<NSThread: 0x604000069240>{number = 1, name = main}
包括主線程在內(nèi),整個(gè)隊(duì)列里面有兩條線程,但是執(zhí)行結(jié)果卻被第一個(gè)sleep阻塞1秒。所以串行隊(duì)列是一個(gè)個(gè)任務(wù)完成后再執(zhí)行后面的任務(wù)
第二步,寫一個(gè)異步線程包裹一個(gè)同步線程,并在同步線程中執(zhí)行run
[self run];
__weak typeof(self) weakself = self;
dispatch_queue_t queue = dispatch_queue_create("串行隊(duì)列", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
[weakself run];
});
dispatch_async(queue, ^{ //1號(hào)任務(wù)
[weakself run];
dispatch_sync(queue, ^{ //2號(hào)任務(wù)
[weakself run];
});
});
結(jié)果:
2017-11-09 14:08:03.335534+0800 MultithreadingDemo[97291:6200400] ---<NSThread: 0x60000006f580>{number = 1, name = main}
2017-11-09 14:08:03.335846+0800 MultithreadingDemo[97291:6200500] ---<NSThread: 0x60000046a9c0>{number = 3, name = (null)}
2017-11-09 14:08:03.336308+0800 MultithreadingDemo[97291:6200500] ---<NSThread: 0x60000046a9c0>{number = 3, name = (null)}
崩潰在 dispatch_sync 這一行,所以我們只看到了一條主線程run的記錄
分析一下:
1、我們使用了同步線程,而且是串行隊(duì)列,
2、1號(hào)任務(wù)沒(méi)有結(jié)束、2號(hào)任務(wù)是無(wú)法執(zhí)行的
3、當(dāng)任務(wù)走到同步線程開啟的時(shí)候,線程會(huì)被阻塞,直到2號(hào)任務(wù)block內(nèi)的任務(wù)執(zhí)行完成才會(huì)釋放
4、可是同步線程把任務(wù)加入queue隊(duì)列之后才發(fā)現(xiàn),自己要執(zhí)行的這個(gè)任務(wù)前面還卡著一個(gè)1號(hào)任務(wù)
5、線程被阻塞,1號(hào)任務(wù)無(wú)法完成,1號(hào)任務(wù)沒(méi)完成 2號(hào)任務(wù)就不能執(zhí)行
6、造成死鎖
所以改一下,只要把同步任務(wù)換個(gè)隊(duì)列執(zhí)行,就可以避免死鎖了:
dispatch_async(queue, ^{
[weakself run];
dispatch_sync(dispatch_get_main_queue(), ^{
[weakself run];
});
});
2.主隊(duì)列:順序執(zhí)行、串行隊(duì)列 一般更新UI都在主線程。
//主隊(duì)列中的任務(wù)一定會(huì)回到主線程去執(zhí)行、如下方式去執(zhí)行,同步任務(wù)在主線程、主隊(duì)列執(zhí)行,主隊(duì)列是串行隊(duì)列,又會(huì)出現(xiàn)死鎖
dispatch_sync(dispatch_get_main_queue(), ^{
[weakself run];
});
改成:
dispatch_async(dispatch_get_main_queue(), ^{
[weakself run];
});
3.并發(fā)隊(duì)列:非順序執(zhí)行,隨機(jī)、同步執(zhí)行并發(fā)隊(duì)列一樣會(huì)卡住主線程
如串行隊(duì)列所寫,在并行隊(duì)列寫相同代碼執(zhí)行結(jié)果會(huì)如何:
[self run];
__weak typeof(self) weakself = self;
dispatch_queue_t queue = dispatch_queue_create("并行隊(duì)列", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[weakself run];
});
dispatch_async(queue, ^{
[weakself run];
dispatch_sync(queue, ^{
[weakself run];
});
});
結(jié)果:
2017-11-09 14:34:15.075513+0800 MultithreadingDemo[97572:6219452] ---<NSThread: 0x60000007dbc0>{number = 1, name = main}
2017-11-09 14:34:15.075965+0800 MultithreadingDemo[97572:6219573] ---<NSThread: 0x60000046bdc0>{number = 4, name = (null)}
2017-11-09 14:34:15.075967+0800 MultithreadingDemo[97572:6219574] ---<NSThread: 0x60000046ae00>{number = 3, name = (null)}
2017-11-09 14:34:15.076703+0800 MultithreadingDemo[97572:6219573] ---<NSThread: 0x60000046bdc0>{number = 4, name = (null)}
可以看到執(zhí)行結(jié)果是正常的,并未出現(xiàn)死鎖,那是因?yàn)椴⑿嘘?duì)列是可以多個(gè)任務(wù)并行執(zhí)行的,正因?yàn)樵试S多個(gè)任務(wù)同時(shí)執(zhí)行,所以執(zhí)行結(jié)束時(shí)間并不是按著添加入隊(duì)列的順序來(lái)的。
4.全球隊(duì)列:并行隊(duì)列、異步線程常用隊(duì)列
dispatch_get_global_queue(0, 0);
2.并發(fā)執(zhí)行迭代循環(huán)
在開發(fā)中,并發(fā)隊(duì)列能很好地提高效率,特別是當(dāng)我們需要執(zhí)行一個(gè)數(shù)據(jù)龐大的循環(huán)操作時(shí)。打個(gè)比方來(lái)說(shuō)吧,我們需要執(zhí)行一個(gè)for循環(huán),每一次循環(huán)操作如下:
for (i = 0; i < count; i++) {
NSLog("%d",i);
}
GCD提供了一個(gè)簡(jiǎn)化方法叫做dispatch_apply,當(dāng)我們把這個(gè)方法放到并發(fā)隊(duì)列中執(zhí)行時(shí),這個(gè)函數(shù)會(huì)調(diào)用單一block多次,并平行運(yùn)算,然后等待所有運(yùn)算結(jié)束。
代碼示例:
但是dispatch_apply函數(shù)是沒(méi)有異步版本的。只能將整個(gè)dispatch_apply 置于異步中。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(count, queue, ^(size_t i) {
NSLog("%d",i);
});
直接在主線程調(diào)用dispatch_apply 會(huì)阻塞主線程,如果使用了并發(fā)隊(duì)列 隊(duì)列任務(wù)會(huì)被放置在異步線程中執(zhí)行,但是主線程依然被阻塞。只有整個(gè)放入異步線程才不會(huì)阻塞主線程。
3.掛起和恢復(fù)隊(duì)列
有時(shí)候,我們不想讓隊(duì)列中的某些任務(wù)馬上執(zhí)行,這時(shí)我們可以通過(guò)掛起操作來(lái)阻止一個(gè)隊(duì)列中將要執(zhí)行的任務(wù)。當(dāng)需要掛起隊(duì)列時(shí),使用dispatch_suspend方法;恢復(fù)隊(duì)列時(shí),使用dispatch_resume方法。調(diào)用dispatch_suspend會(huì)增加隊(duì)列掛起的引用計(jì)數(shù),而調(diào)用dispatch_resume則會(huì)減少引用計(jì)數(shù),當(dāng)引用計(jì)數(shù)大于0時(shí),隊(duì)列會(huì)保持掛起狀態(tài)。因此,這隊(duì)列的掛起和恢復(fù)中,我們需要小心使用以避免引用計(jì)數(shù)計(jì)算錯(cuò)誤的出現(xiàn)。
執(zhí)行掛起操作不會(huì)對(duì)已經(jīng)開始執(zhí)行的任務(wù)起作用,它僅僅只會(huì)阻止將要進(jìn)行但是還未開始的任務(wù)。
dispatch_queue_t myQueue;
myQueue = dispatch_queue_create("隊(duì)列", NULL);
//掛起隊(duì)列
dispatch_suspend(myQueue);
//恢復(fù)隊(duì)列
dispatch_resume(myQueue);
如下:
__weak typeof(self) weakself = self;
dispatch_queue_t queue = dispatch_queue_create("并行隊(duì)列", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[weakself run];
});
dispatch_async(queue, ^{
dispatch_suspend(queue);
[weakself run];
dispatch_sync(queue, ^{
[weakself run];
});
});
結(jié)果 只有兩條run語(yǔ)句,同步線程因?yàn)殛?duì)列被掛起,所以并未執(zhí)行
2017-11-09 14:43:22.593056+0800 MultithreadingDemo[97644:6225319] ---<NSThread: 0x60000027e0c0>{number = 9, name = (null)}
2017-11-09 14:43:22.592831+0800 MultithreadingDemo[97644:6226170] ---<NSThread: 0x600000271a40>{number = 8, name = (null)}
4.dispatch_after 的使用
延遲一段時(shí)間把一項(xiàng)任務(wù)提交到隊(duì)列中執(zhí)行,返回之后就不能取消
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
一般我們?cè)谧鲆恍┭訒r(shí)任務(wù)的時(shí)候使用的多
5.dispatch_once 的使用
保證在APP運(yùn)行期間,block中的代碼只執(zhí)行一次
static Demo *demo;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
demo = [Demo new];
});
單例常用
6.Dispatch Groups 的使用
Dispatch groups是阻塞線程直到一個(gè)或多個(gè)任務(wù)完成的一種方式。在那些需要等待任務(wù)完成才能執(zhí)行某個(gè)處理的時(shí)候,你可以使用這個(gè)方法。Group會(huì)在整個(gè)組的任務(wù)都完成時(shí)通知你,這些任務(wù)可以是同步的,也可以是異步的,即便在不同的隊(duì)列也行。而且在整個(gè)組的任務(wù)都完成時(shí), Group可以用同步的或者異步的方式通知你。當(dāng)group中所有的任務(wù)都完成時(shí),GCD 提供了兩種通知方式。
dispatch_group_wait。它會(huì)阻塞當(dāng)前線程,直到隊(duì)列里面所有的任務(wù)都完成或者等到某個(gè)超時(shí)發(fā)生。
代碼示例:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
// 添加隊(duì)列到組中
dispatch_group_async(group, queue, ^{
// 一些異步操作 或者耗時(shí)操作
});
//如果在所有任務(wù)完成前超時(shí)了,該函數(shù)會(huì)返回一個(gè)非零值。
//你可以對(duì)此返回值做條件判斷以確定是否超出等待周期;
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"123"); //被阻塞,因?yàn)閐ispatch_group_wait 所以這一句代碼只會(huì)在隊(duì)列任務(wù)都完成后執(zhí)行
dispatch_group_notify。它以異步的方式工作,當(dāng) Dispatch Group中沒(méi)有任何任務(wù)時(shí),它就會(huì)執(zhí)行其代碼,那么 completionBlock便會(huì)運(yùn)行??梢杂糜谠诓⑿嘘?duì)列中待所有任務(wù)都完成之后再調(diào)起執(zhí)行。
代碼示例:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
// 添加隊(duì)列到組中
dispatch_group_async(group, queue, ^{
NSLog(@"one---%@",NSThread.currentThread);
});
dispatch_group_async(group, queue, ^{
// 一些延時(shí)操作
sleep(2);
NSLog(@"two---%@",NSThread.currentThread);
});
dispatch_group_async(group, queue, ^{
// 一些延時(shí)操作
sleep(3);
NSLog(@"three---%@",NSThread.currentThread);
});
dispatch_group_async(group, queue, ^{
NSLog(@"four---%@",NSThread.currentThread);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"我會(huì)一直等到現(xiàn)在");
});
NSLog(@"123");
結(jié)果
2017-11-09 15:21:48.480021+0800 MultithreadingDemo[98195:6255855] 123
2017-11-09 15:21:48.480192+0800 MultithreadingDemo[98195:6255916] one---<NSThread: 0x600000466800>{number = 3, name = (null)}
2017-11-09 15:21:48.480321+0800 MultithreadingDemo[98195:6255917] four---<NSThread: 0x600000466840>{number = 4, name = (null)}
2017-11-09 15:21:50.483266+0800 MultithreadingDemo[98195:6255918] two---<NSThread: 0x604000462e80>{number = 5, name = (null)}
2017-11-09 15:21:51.483851+0800 MultithreadingDemo[98195:6255922] three---<NSThread: 0x60400027dd40>{number = 6, name = (null)}
2017-11-09 15:21:51.484084+0800 MultithreadingDemo[98195:6255922] 我會(huì)一直等到現(xiàn)在
對(duì)這一段代碼,并行隊(duì)列執(zhí)行,最后一行不會(huì)阻塞,其余加入group中的任務(wù)執(zhí)行完成后才會(huì)執(zhí)行notify中的任務(wù)。
常用于需要等待某些異步線程執(zhí)行完成后統(tǒng)一處理的場(chǎng)景,比如多個(gè)接口數(shù)據(jù)拼裝模型
7.dispatch_barrier_async 、dispatch_barrier_sync 的使用
在并行隊(duì)列中,為了保持某些任務(wù)的順序,需要等待一些任務(wù)完成后才能繼續(xù)進(jìn)行,使用 barrier 柵欄函數(shù) 來(lái)等待之前任務(wù)完成,避免數(shù)據(jù)競(jìng)爭(zhēng)等問(wèn)題。
同步,會(huì)攔截后面所有的代碼執(zhí)行,直到前面任務(wù)完成,并且完成柵欄函數(shù)中的任務(wù)。
異步,攔截并行隊(duì)列中的后續(xù)任務(wù),直到前面任務(wù)執(zhí)行完,并且完成柵欄函數(shù)中的任務(wù)。不會(huì)影響主線程。
dispatch_barrier_async 函數(shù)會(huì)等待追加到并行隊(duì)列中的操作全部執(zhí)行完之后,然后再執(zhí)行 dispatch_barrier_async 函數(shù)追加的處理,等 dispatch_barrier_async 追加的處理執(zhí)行結(jié)束之后(同時(shí)只執(zhí)行一個(gè)任務(wù)),Concurrent Dispatch Queue才恢復(fù)之前的動(dòng)作繼續(xù)執(zhí)行。
注意:使用 dispatch_barrier_async,該函數(shù)只能搭配自定義并行隊(duì)列 dispatch_queue_t 使用。不能使用: dispatch_get_global_queue ,否則 dispatch_barrier_async 的作用會(huì)和 dispatch_async 的作用一模一樣。
__weak typeof(self) weakself = self;
dispatch_queue_t queue = dispatch_queue_create("并行隊(duì)列", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[weakself run];
});
dispatch_async(queue, ^{
[weakself run];
});
//加入
dispatch_barrier_async(queue, ^{
sleep(1);
[weakself run2];
sleep(1);
});
dispatch_barrier_async(queue, ^{
[weakself run2];
sleep(1);
});
dispatch_async(queue, ^{
[weakself run];
});
dispatch_async(queue, ^{
[weakself run];
});
結(jié)果
2017-11-09 16:50:54.226018+0800 MultithreadingDemo[99305:6326134] ---<NSThread: 0x6000002617c0>{number = 4, name = (null)}
2017-11-09 16:50:54.225967+0800 MultithreadingDemo[99305:6326323] ---<NSThread: 0x600000268700>{number = 3, name = (null)}
2017-11-09 16:50:55.227973+0800 MultithreadingDemo[99305:6326323] ++++<NSThread: 0x600000268700>{number = 3, name = (null)}
2017-11-09 16:50:56.228820+0800 MultithreadingDemo[99305:6326323] ++++<NSThread: 0x600000268700>{number = 3, name = (null)}
2017-11-09 16:50:57.230081+0800 MultithreadingDemo[99305:6326323] ---<NSThread: 0x600000268700>{number = 3, name = (null)}
2017-11-09 16:50:57.230082+0800 MultithreadingDemo[99305:6326134] ---<NSThread: 0x6000002617c0>{number = 4, name = (null)}
55\56秒 明顯的三次停頓。說(shuō)明執(zhí)行 dispatch_barrier_async 插入的任務(wù)時(shí) 同時(shí)只執(zhí)行了一個(gè)任務(wù)
3、NSOperation
NSOperation 是蘋果公司對(duì) GCD 的封裝,完全面向?qū)ο?,所以使用起?lái)更好理解。 大家可以看到 NSOperation和 NSOperationQueue 分別對(duì)應(yīng) GCD 的 任務(wù) 和 隊(duì)列 。
優(yōu)缺點(diǎn)
與NSThread的區(qū)別:沒(méi)有那么輕量級(jí),但是不需要關(guān)心線程管理,數(shù)據(jù)同步的事情。
與GCD區(qū)別:NSOperationQueue可以方便的管理并發(fā)、NSOperation之間的優(yōu)先級(jí)。GCD主要與block結(jié)合使用。代碼簡(jiǎn)潔高效。
如果異步操作的過(guò)程需要更多的被交互和UI呈現(xiàn)出來(lái),NSOperationQueue會(huì)是一個(gè)更好的選擇。底層代碼中,任務(wù)之間不太互相依賴,而需要更高的并發(fā)能力,GCD則更有優(yōu)勢(shì)
我們要做的就是:
1.將要執(zhí)行的任務(wù)封裝到一個(gè)NSOperation對(duì)象中
2.將此任務(wù)添加到一個(gè)NSOperationQueue對(duì)象中
創(chuàng)建添加
NSOperation有兩個(gè)子類:NSBlockOperation 和 NSInvocationOperation (或者自行自定義Operation )
NSBlockOperation:(OC 代碼、Swift也有)
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
- (void)addExecutionBlock:(void (^)(void))block;
__weak typeof(self) weakself = self;
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
[weakself run];
}];
[operation start];
結(jié)果
2017-11-09 15:53:29.765058+0800 MultithreadingDemo[98518:6280532] ---<NSThread: 0x604000073e80>{number = 1, name = main}
1、直接執(zhí)行創(chuàng)建的operation 默認(rèn)是當(dāng)前線程
2、NSBlockOperation 還有一個(gè)添加執(zhí)行block的方法,它會(huì)在當(dāng)前線程和其他多個(gè)線程執(zhí)行這些block中的任務(wù)
[operation addExecutionBlock:^{
[weakself run];
}];
結(jié)果
2017-11-09 15:53:29.765058+0800 MultithreadingDemo[98518:6280532] ---<NSThread: 0x604000073e80>{number = 1, name = main}
2017-11-09 15:53:29.765055+0800 MultithreadingDemo[98518:6280642] ---<NSThread: 0x60400026ea40>{number = 3, name = (null)}
注意:當(dāng)NSOperation開始執(zhí)行后不能再添加任務(wù)
NSInvocationOperation: (Swift 不允許使用)
- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
- (instancetype)initWithInvocation:(NSInvocation *)inv NS_DESIGNATED_INITIALIZER;
//1.創(chuàng)建NSInvocationOperation對(duì)象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//2.開始執(zhí)行
[operation start];
隊(duì)列
上面例子中的任務(wù)執(zhí)行,不管是多線程還是單線程都必然會(huì)在當(dāng)前線程執(zhí)行一個(gè)任務(wù)
NSOperation的隊(duì)列和GCD不同,不存在串行、并行之分,他們只有主隊(duì)列和其他隊(duì)列:
主隊(duì)列:
NSOperationQueue *queue = [NSOperationQueue mainQueue];
其他隊(duì)列:(注意:其他隊(duì)列的任務(wù)會(huì)在其他線程并行執(zhí)行)
所有的非主隊(duì)列就是其他隊(duì)列,也就是說(shuō)不是通過(guò) mainQueue 獲取的隊(duì)列都是其他隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
__weak typeof(self) weakself = self;
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
[weakself run];
}];
[operation addExecutionBlock:^{
[weakself run];
}];
// [operation start]; 只要加入隊(duì)列,任務(wù)就會(huì)自動(dòng)start
[queue addOperation:operation];
或者
[queue addOperationWithBlock:^{
[weakself run];
}];
其實(shí)更多來(lái)看 NSOperation相當(dāng)于一個(gè)任務(wù)組,里面可以裝多個(gè)任務(wù),然后任務(wù)組被加入隊(duì)列去執(zhí)行
那么問(wèn)題來(lái)了:沒(méi)有串行隊(duì)列么?按前面說(shuō)的,所有任務(wù)會(huì)在其他線程同步執(zhí)行,那我希望一個(gè)個(gè)執(zhí)行怎么辦?
NSOperationQueue 有一個(gè)參數(shù):maxConcurrentOperationCount
這個(gè)參數(shù)表示允許并發(fā)執(zhí)行的任務(wù)數(shù)限制,當(dāng)為1的時(shí)候其實(shí)也就是串行執(zhí)行了
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
__weak typeof(self) weakself = self;
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
[weakself run];
}];
[operation addExecutionBlock:^{
sleep(1);
[weakself run];
}];
queue.maxConcurrentOperationCount = 1;
[queue addOperation:operation];
[queue addOperationWithBlock:^{
[weakself run];
}];
結(jié)果
2017-11-09 16:18:23.524428+0800 MultithreadingDemo[98831:6301089] ---<NSThread: 0x600000473840>{number = 3, name = (null)}
2017-11-09 16:18:24.524800+0800 MultithreadingDemo[98831:6301087] ---<NSThread: 0x60000046d640>{number = 4, name = (null)}
2017-11-09 16:18:24.525121+0800 MultithreadingDemo[98831:6301087] ---<NSThread: 0x60000046d640>{number = 4, name = (null)}
其他功能
依賴:NSOperation還有一個(gè)非常實(shí)用的功能,也就是添加依賴
NSBlockOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
sleep(1);
NSLog(@"拉取A接口--%@",NSThread.currentThread);
}];
NSBlockOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
sleep(1);
NSLog(@"通過(guò)A接口參數(shù)拉取B接口--%@",NSThread.currentThread);
}];
NSBlockOperation *operationC = [NSBlockOperation blockOperationWithBlock:^{
sleep(1);
NSLog(@"通過(guò)B接口參數(shù)拉取C接口--%@",NSThread.currentThread);
}];
[operationB addDependency:operationA];
[operationC addDependency:operationB];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operationA, operationB, operationC] waitUntilFinished:NO];
隊(duì)列允許多個(gè)任務(wù)同時(shí)執(zhí)行,但因?yàn)槿齻€(gè)任務(wù)之間的依賴,我們看一下結(jié)果:
2017-11-09 16:25:56.598192+0800 MultithreadingDemo[98972:6307395] 拉取A接口--<NSThread: 0x6040002748c0>{number = 3, name = (null)}
2017-11-09 16:25:57.599920+0800 MultithreadingDemo[98972:6307396] 通過(guò)A接口參數(shù)拉取B接口--<NSThread: 0x60000046d680>{number = 4, name = (null)}
2017-11-09 16:25:58.600665+0800 MultithreadingDemo[98972:6307395] 通過(guò)B接口參數(shù)拉取C接口--<NSThread: 0x6040002748c0>{number = 3, name = (null)}
注意:
使用依賴的時(shí)候,我們要注意一點(diǎn),依賴不能產(chǎn)生循環(huán)依賴,不然會(huì)死鎖
可以使用 removeDependency 來(lái)解除依賴關(guān)系。
不同的隊(duì)列之間的任務(wù)也可以依賴
4、鎖
NSLock
NSLock 遵循 NSLocking 協(xié)議,
lock 方法是加鎖
unlock 是解鎖
tryLock 是嘗試加鎖,如果失敗的話返回 NO
lockBeforeDate: 是在指定Date之前嘗試加鎖,如果在指定時(shí)間之前都不能加鎖,則返回NO。
NSConditionLock 條件鎖
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
NSConditionLock 和 NSLock 類似,都遵循 NSLocking 協(xié)議,方法都類似,只是多了一個(gè) condition 屬性,以及每個(gè)操作都多了一個(gè)關(guān)于 condition 屬性的方法
NSConditionLock 可以稱為條件鎖:
tryLockWhenCondition:(NSInteger)condition; 只有 condition 參數(shù)與初始化時(shí)候的 condition 相等,lock 才能正確進(jìn)行加鎖操作。
unlockWithCondition:(NSInteger)condition; 解鎖后 condition 的值更新為新的值
NSRecursiveLock 遞歸鎖
NSRecursiveLock 是遞歸鎖,他和 NSLock 的區(qū)別在于,NSRecursiveLock 可以在一個(gè)線程中重復(fù)加鎖(反正單線程內(nèi)任務(wù)是按順序執(zhí)行的,不會(huì)出現(xiàn)資源競(jìng)爭(zhēng)問(wèn)題),NSRecursiveLock 會(huì)記錄上鎖和解鎖的次數(shù),當(dāng)二者平衡的時(shí)候,才會(huì)釋放鎖,其它線程才可以上鎖成功。
如下遞歸操作,block中每次有加鎖操作,再未解鎖的時(shí)候再次進(jìn)入遞歸,再次加鎖,造成死鎖。NSRecursiveLock就是用來(lái)解決這個(gè)問(wèn)題的。
NSLock *normal_lock = [NSLock new];
NSRecursiveLock *recu_lock = [NSRecursiveLock new];
//線程1
dispatch_async(dispatch_get_main_queue(), ^{
static void (^Block)(int);
Block = ^(int value) {
[normal_lock lock];
if (value > 0) {
NSLog(@"value:%d", value);
Block(value - 1);
}
[normal_lock unlock];
};
Block(5);
});
NSCondition
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
NSCondition 的對(duì)象實(shí)際上作為一個(gè)鎖和一個(gè)線程檢查器,鎖上之后其它線程也能上鎖,而之后可以根據(jù)條件決定是否繼續(xù)運(yùn)行線程,即線程是否要進(jìn)入 waiting 狀態(tài),經(jīng)測(cè)試,NSCondition 并不會(huì)像上文的那些鎖一樣,先輪詢,而是直接進(jìn)入 waiting 狀態(tài),當(dāng)其它線程中的該鎖執(zhí)行 signal 或者 broadcast 方法時(shí),線程被喚醒,繼續(xù)運(yùn)行之后的方法。
用法如下:
NSCondition *lock = [[NSCondition alloc] init];
NSMutableArray *array = [[NSMutableArray alloc] init];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
while (!array.count) {
[lock wait];
}
[array removeAllObjects];
NSLog(@"array removeAllObjects");
[lock unlock];
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);//以保證讓線程2的代碼后執(zhí)行
[lock lock];
[array addObject:@1];
NSLog(@"array addObject:@1");
[lock signal];
[lock unlock];
});
也就是使用 NSCondition 的模型為:
鎖定條件對(duì)象。
測(cè)試是否可以安全的履行接下來(lái)的任務(wù)。
如果布爾值是假的,調(diào)用條件對(duì)象的 wait 或 waitUntilDate: 方法來(lái)阻塞線程。 在從這些方法返回,則轉(zhuǎn)到步驟 2 重新測(cè)試你的布爾值。 (繼續(xù)等待信號(hào)和重新測(cè)試,直到可以安全的履行接下來(lái)的任務(wù)。waitUntilDate: 方法有個(gè)等待時(shí)間限制,指定的時(shí)間到了,則放回 NO,繼續(xù)運(yùn)行接下來(lái)的任務(wù))
如果布爾值為真,執(zhí)行接下來(lái)的任務(wù)。
當(dāng)任務(wù)完成后,解鎖條件對(duì)象。
而步驟 3 說(shuō)的等待的信號(hào),既線程 2 執(zhí)行 [lock signal] 發(fā)送的信號(hào)。
其中 signal 和 broadcast 方法的區(qū)別在于,signal 只是一個(gè)信號(hào)量,只能喚醒一個(gè)等待的線程,想喚醒多個(gè)就得多次調(diào)用,而 broadcast 可以喚醒所有在等待的線程。如果沒(méi)有等待的線程,這兩個(gè)方法都沒(méi)有作用。
@synchronized代碼塊
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self) {
sleep(2);
NSLog(@"線程1");
}
NSLog(@"線程1解鎖成功");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
@synchronized(self) {
NSLog(@"線程2");
}
});
@synchronized(object) 指令使用的 object 為該鎖的唯一標(biāo)識(shí),只有當(dāng)標(biāo)識(shí)相同時(shí),才滿足互斥,所以如果線程 2 中的 @synchronized(self) 改為@synchronized(self.view),則線程2就不會(huì)被阻塞。
@synchronized 指令實(shí)現(xiàn)鎖的優(yōu)點(diǎn)就是我們不需要在代碼中顯式的創(chuàng)建鎖對(duì)象,便可以實(shí)現(xiàn)鎖的機(jī)制,但作為一種預(yù)防措施,@synchronized 塊會(huì)隱式的添加一個(gè)異常處理例程來(lái)保護(hù)代碼,該處理例程會(huì)在異常拋出的時(shí)候自動(dòng)的釋放互斥鎖。
@synchronized 還有一個(gè)好處就是不用擔(dān)心忘記解鎖了。
如果在 @sychronized(object){} 內(nèi)部 object 被釋放或被設(shè)為 nil,從我做的測(cè)試的結(jié)果來(lái)看,的確沒(méi)有問(wèn)題,但如果 object 一開始就是 nil,則失去了鎖的功能。不過(guò)雖然 nil 不行,但 @synchronized([NSNull null]) 是完全可以的。
條件信號(hào)量 dispatch_semaphore_t
dispatch_semaphore 是 GCD 用來(lái)同步的一種方式,與他相關(guān)的只有三個(gè)函數(shù),一個(gè)是創(chuàng)建信號(hào)量,一個(gè)是等待信號(hào),一個(gè)是發(fā)送信號(hào)。 有點(diǎn)和NSCondition類似,都是一種基于信號(hào)的同步方式。但 NSCondition 信號(hào)只能發(fā)送,不能保存(如果沒(méi)有線程在等待,則發(fā)送的信號(hào)會(huì)失效)而 dispatch_semaphore 能保存發(fā)送的信號(hào)。dispatch_semaphore 的核心是 dispatch_semaphore_t 類型的信號(hào)量。
dispatch_semaphore_create(long value);
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(signal, overTime);
sleep(1);
NSLog(@"線程1");
dispatch_semaphore_signal(signal);
NSLog(@"%@",signal);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(signal, overTime);
sleep(1);
NSLog(@"線程2");
dispatch_semaphore_signal(signal);
NSLog(@"%@",signal);
});
dispatch_semaphore_wait(signal, overTime); 方法會(huì)判斷 signal 的信號(hào)值是否大于 0。大于 0 不會(huì)阻塞線程,消耗掉一個(gè)信號(hào),執(zhí)行后續(xù)任務(wù)。如果信號(hào)值為 0,該線程會(huì)和 NSCondition 一樣直接進(jìn)入 waiting 狀態(tài),等待其他線程發(fā)送信號(hào)喚醒線程去執(zhí)行后續(xù)任務(wù),或者當(dāng) overTime 時(shí)限到了,也會(huì)執(zhí)行后續(xù)任務(wù)。
dispatch_semaphore_signal(signal); 發(fā)送信號(hào),如果沒(méi)有等待的線程接受信號(hào),則使 signal 信號(hào)值加一(做到對(duì)信號(hào)的保存)。
從上面的實(shí)例代碼可以看到,一個(gè) dispatch_semaphore_wait(signal, overTime); 方法會(huì)去對(duì)應(yīng)一個(gè) dispatch_semaphore_signal(signal); 看起來(lái)像 NSLock 的 lock 和 unlock,其實(shí)可以這樣理解,區(qū)別只在于有信號(hào)量這個(gè)參數(shù),lock unlock 只能同一時(shí)間,一個(gè)線程訪問(wèn)被保護(hù)的臨界區(qū),而如果 dispatch_semaphore 的信號(hào)量初始值為 x ,則可以有 x 個(gè)線程同時(shí)訪問(wèn)被保護(hù)的臨界區(qū)。
OSSpinLock 自旋鎖
OSSpinLock 是一種自旋鎖,也只有加鎖,解鎖,嘗試加鎖三個(gè)方法。和 NSLock 不同的是 NSLock 請(qǐng)求加鎖失敗的話,會(huì)先輪詢,但一秒過(guò)后便會(huì)使線程進(jìn)入 waiting 狀態(tài),等待喚醒。而 OSSpinLock 會(huì)一直輪詢,等待時(shí)會(huì)消耗大量 CPU 資源,不適用于較長(zhǎng)時(shí)間的任務(wù)。
__block OSSpinLock theLock = OS_SPINLOCK_INIT;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OSSpinLockLock(&theLock);
NSLog(@"線程1");
sleep(10);
OSSpinLockUnlock(&theLock);
NSLog(@"線程1解鎖成功");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
OSSpinLockLock(&theLock);
NSLog(@"線程2");
OSSpinLockUnlock(&theLock);
});
ThreadLockControlDemo[2856:316247] 線程1
ThreadLockControlDemo[2856:316247] 線程1解鎖成功
ThreadLockControlDemo[2856:316260] 線程2
拿上面的輸出結(jié)果和上文 NSLock 的輸出結(jié)果做對(duì)比,會(huì)發(fā)現(xiàn) sleep(10) 的情況,OSSpinLock 中的“線程 2”并沒(méi)有和”線程 1解鎖成功“在一個(gè)時(shí)間輸出,而 NSLock 這里是同一時(shí)間輸出,而是有一點(diǎn)時(shí)間間隔,所以 OSSpinLock 一直在做著輪詢,而不是像 NSLock 一樣先輪詢,再 waiting 等喚醒。
5、常見問(wèn)題
dispatch_release 已被廢棄(6.0)dispatch_release在6.0以后內(nèi)部被改成對(duì)象釋放(release)所以 arc后都不再使用。
app啟動(dòng),系統(tǒng)默認(rèn)創(chuàng)建5個(gè)線程
NSTimer
[self.timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:5]];//暫停5s
6、捕獲開發(fā)中子線程更新UI的邏輯
1.為什么UI要在主線程更新
因?yàn)閁IKit不是線程安全的。試想下面這幾種情況:
兩個(gè)線程同時(shí)設(shè)置同一個(gè)背景圖片,那么很有可能因?yàn)楫?dāng)前圖片被釋放了兩次而導(dǎo)致應(yīng)用崩潰。
兩個(gè)線程同時(shí)設(shè)置同一個(gè)UIView的背景顏色,那么很有可能渲染顯示的是顏色A,而此時(shí)在UIView邏輯樹上的背景顏色屬性為B。
兩個(gè)線程同時(shí)操作view的樹形結(jié)構(gòu):在線程A中for循環(huán)遍歷并操作當(dāng)前View的所有subView,然后此時(shí)線程B中將某個(gè)subView直接刪除,這就導(dǎo)致了錯(cuò)亂還可能導(dǎo)致應(yīng)用崩潰。
iOS4之后蘋果將大部分繪圖的方法和諸如 UIColor 和 UIFont 這樣的類改寫為了線程安全可用,但是仍然強(qiáng)烈建議講UI操作保證在主線程中執(zhí)行。
2.我的想法
View的更新操作 使用runtime 去替換 View 中實(shí)現(xiàn) 的方法 不變更實(shí)現(xiàn)。只是在中間插入 線程檢查操作,發(fā)現(xiàn)子線程就必須打印線程調(diào)用棧并觸發(fā)crash。
問(wèn)題:替換哪些方法更合適? 都會(huì)涉及到哪些基礎(chǔ)控件需要category?
3.例子
1.創(chuàng)建一個(gè)UIImage的category
@implementation UIImage (demo)
+(void)load
{
Method m1 = class_getClassMethod([UIImage class],@selector(imageNamed:));
Method m2 = class_getClassMethod([UIImage class],@selector(ximageNamed:));
// 開始交換方法實(shí)現(xiàn)
method_exchangeImplementations(m1, m2);
}
+(UIImage *)ximageNamed:(NSString *)name
{
NSLog(@"進(jìn)入方法-開始檢查線程");
NSThread *thread = [NSThread currentThread];
if (![thread isMainThread]) {
NSLog(@" 當(dāng)前線程不是主線程 %@",[NSThread callStackSymbols]);
}
return [UIImage ximageNamed:name];
}
@end
2.在一個(gè)視圖內(nèi)實(shí)現(xiàn)一段UIImage的異步賦予圖片
UIImageView *img = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 300, 300)];
[self.view addSubview:img];
img.image = [UIImage imageNamed:@"networklosed"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
img.image = [UIImage imageNamed:@"mncg_search_nor"];
});
NSLog(@"測(cè)試線程是否異步");