iOS多線程開發(fā)必須知道的概念名詞:
1. 進程
- 進程(process)就是一個正在執(zhí)行的程序的實例。也就是說我們的每一個APP程序在執(zhí)行時實例都是一個進程,也可以說在APP執(zhí)行時,它只擁有唯一的一個進程。
- 每一個進程都是獨立的,每一個進程均在專屬的內(nèi)存空間內(nèi),iOS中每一個App(一個進程)都有自己獨特的內(nèi)存和磁盤空間,別的App(進程)是不允許訪問的(越獄除外)。iOS開發(fā)中應(yīng)用程序之間互相調(diào)用包括調(diào)用發(fā)短信、打電話等進程相關(guān)操作的API都被封裝到
UIApplication
這個類中了。 - 每個進程至少擁有一個線程。
- 在UNIX和Linux系統(tǒng)中是有進程層次結(jié)構(gòu)的,當進程創(chuàng)建了另一個進程后,父進程和子進程就以某種方式繼續(xù)保持聯(lián)系。子進程自身可以創(chuàng)建更多的進程,組成一個進程的層次結(jié)構(gòu)。(進程只有一個父進程但是可以有0個,1個或多個子進程。)而Windows中沒有進程層次的概念,所有進程地位相同。唯一類似進程層次的地方是在創(chuàng)建進程的時候父進程得到一個特別的令牌(稱為句柄),該令牌可以用來控制子進程。但是父進程可以把這個令牌傳送給其他進程,這樣就不存在進程層次了。
- 進程有3種狀態(tài),分別是:
1)運行態(tài)(該時刻進程實際占用CPU)。
2)就緒態(tài)(可運行,但因為其他進程正在運行而暫時停止)。
3)阻塞態(tài)(除非某種外部事件發(fā)生,否則進程不能運行)。
2. 線程
- 進程用于把資源集中到一起,而線程是在CUP上被調(diào)度的實體。
- 線程擁有自己的寄存器,用來保存當前的工作變量,稱作線程的上下文。還擁有一個自己的堆棧,用來記錄執(zhí)行歷史。
- 線程之間共享同樣的內(nèi)存空間和全局變量,一個線程可以讀、寫或甚至清除另一個線程的堆棧。
- 如果多個線程都是CPU密集型(也稱計算密集型)那并不能獲得性能上的增強,如果是I/O密集型則能加快程序執(zhí)行速度。
進程和線程的關(guān)系
- 線程只是一個進程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,它們共享進程的地址空間。一個線程crash就等于整個進程crash。
-
進程的創(chuàng)建和操作開銷很大,某種意義上講線程就相當于輕量級的進程。多線程使用的其中一個理由就是因為線程比進程更輕量級,線程比進程更容易(即更快)的創(chuàng)建和撤銷。在許多系統(tǒng)中,創(chuàng)建一個線程比創(chuàng)建一個進程要快10~100倍。
進程線程關(guān)系示意圖.jpeg
多線程(英語:multithreading),是指從軟件或者硬件上實現(xiàn)多個線程并發(fā)執(zhí)行的技術(shù)。
多線程好處多的同時也易引發(fā)一些問題,“數(shù)據(jù)競爭”-多個線程操作同一資源時可能會導致數(shù)據(jù)的不一致;“死鎖”-兩個或兩個以上的線程在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象;使用太多的線程會消耗大量內(nèi)存,因為每個線程都有自己的寄存器。進程太多的時候也有消耗太大的問題。
3. 串行(serial) & 并發(fā)(concurrent)& 并行
串行
是同步線程的實現(xiàn)方式,就是任務(wù)A執(zhí)行結(jié)束才能開始執(zhí)行B,單個線程只能執(zhí)行一個任務(wù)。
并發(fā)
和并行
其實是異步線程實現(xiàn)的兩種形式。并行
其實是真正的異步,多核CUP可以同時開啟多條線程供多個任務(wù)同時執(zhí)行,互不干擾。并發(fā)
是偽異步,單個CUP一個時刻只能有一個線程執(zhí)行,想執(zhí)行多個任務(wù)就必須不斷切換執(zhí)行任務(wù)的線程。
4. 同步 & 異步
同步
:多個任務(wù)情況下,一個任務(wù)A執(zhí)行結(jié)束,才可以執(zhí)行另一個任務(wù)B。只存在一個線程。
異步
:多個任務(wù)情況下,一個任務(wù)A正在執(zhí)行,同時可以執(zhí)行另一個任務(wù)B。任務(wù)B不用等待任務(wù)A結(jié)束才執(zhí)行。存在多條線程。
5. 調(diào)度隊列(Dispatch Queue)
調(diào)度隊列
是執(zhí)行處理的隊列也是GCD的基本概念,它按照執(zhí)行任務(wù)添加的順序(即FIFO-先進先出順序)執(zhí)行處理。調(diào)度隊列
在執(zhí)行處理時存在兩種Dispatch Queue,一種是等待現(xiàn)在執(zhí)行中處理的Serial Dispatch Queue
,另一種是不等待現(xiàn)在執(zhí)行中處理的Concurrent Dispatch Queue
,稍后會對這兩種隊列詳細的介紹。官方的說法是有三種隊列,還要一種叫Main dispatch queue
,Main dispatch queue
其實也可以歸為Serial Dispatch Queue
,不過由于它是主線程隊列所以單拿了出來。
iOS多線程技術(shù)對比
-
pthread
pthread(POSIX thread)
是一套通用的多線程API,適用于Unix、Linux、Windows等系統(tǒng),跨平臺、可移植的C語言框架,線程生命周期由開發(fā)者管理,使用難度大。GCD的底層實現(xiàn)庫中也有用到Libc(pthreads)
。
-
NSThread
NSThread
是這幾種方法里面相對輕量級的,但需要管理線程的生命周期、同步、加鎖問題,這會導致一定的性能開銷,同時在多個線程開發(fā)時不便于開發(fā)維護。詳細使用可參考這篇博客。
-
NSOperation
-
NSOperation
是基于OC實現(xiàn)的,它以面向?qū)ο蟮姆绞椒庋b了需要執(zhí)行的操作,然后可以將這個操作放到一個NSOperationQueue
中去異步執(zhí)行。它是線程安全的,開發(fā)者不必關(guān)心線程管理、同步等問題。 -
NSOperation
類是一個抽象類來封裝一個任務(wù)相關(guān)的代碼和數(shù)據(jù),不能直接被使用。可以使用它的兩個子類
NSInvocationOperation
或NSBlockoperation
來執(zhí)行實際的任務(wù),當然你也可以自己封裝一個子類來實現(xiàn)(只需要重載-(void)main
這個方法,在這個方法里面添加需要執(zhí)行的操作。)。 -
NSOperation
可以取消添加的執(zhí)行任務(wù)。一個NSOperation
對象是一個單次對象(single-shot object)只能執(zhí)行一次任務(wù),不能再次執(zhí)行它。
-
GCD
Grand Central Dispatch(GCD)
是蘋果開發(fā)的基于XNU內(nèi)核級線程管理技術(shù),優(yōu)化對多核處理器的支持。GCD是一個基于線程池的任務(wù)并行執(zhí)行模式。其基本思想是將線程池的管理從開發(fā)人員手中轉(zhuǎn)移出來,并更接近操作系統(tǒng)(更高效)。開發(fā)人員不用管理線程,只需要把任務(wù)添加到執(zhí)行隊列就可以。
GCD基于C語言實現(xiàn),不過使用了Block,因而API非常簡潔易用。
GCD需要開發(fā)者釋放自己創(chuàng)建的隊列
Dispatch Queue
,系統(tǒng)提供的標準隊列是全局的所以不用釋放。-
NSOperation相對于GCD的優(yōu)勢:
1>任務(wù)可以添加依賴關(guān)系,即便是異步執(zhí)行也可以給部分任務(wù)執(zhí)行順序;
2>添加的任務(wù)如果不是已經(jīng)執(zhí)行,取消是比較方便的;
3>可以監(jiān)聽任務(wù)的狀態(tài)進而做其他的處理;
4>任務(wù)優(yōu)先級設(shè)置比較方便,GCD可以給隊列設(shè)置優(yōu)先級而且只有3種;
5>最大并發(fā)數(shù)設(shè)置;GCD實現(xiàn)比較復雜些(用信號量);
6>繼承NSOperation自定義。
NSOperation
NSOperation
實現(xiàn)多線程主要步驟是:
1> 封裝執(zhí)行的操作到一個NSOperation
對象中
2> 將封裝的NSOperation
對象添加到NSOperationQueue
中
3> 系統(tǒng)會自動為NSOperation
對象封裝的任務(wù)開啟一條線程執(zhí)行 或者 不加入隊列調(diào)用-(void)start:
在主線程執(zhí)行
- (void)operationManage{
// 這樣在主線程執(zhí)行其實是畫蛇添足的,只是為了做說明而寫
NSBlockOperation *downloadImgPng = [NSBlockOperation blockOperationWithBlock:^{
//downloadImage 任務(wù)
NSLog(@"png -- 當前線程%@",[NSThread currentThread]);
}];
[downloadImgPng start];
}
// NSInvocationOperation執(zhí)行方式
- (void)operationManage{
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage) object:nil];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
// 同一時間最多開啟的線程數(shù)
operationQueue.maxConcurrentOperationCount = 6;
[operationQueue addOperation:invocationOperation];
// 取消所有隊列中的任務(wù)
// [operationQueue cancelAllOperations];
// 取消執(zhí)行的任務(wù)
// [invocationOperation cancel];
}
- (void)downloadImage{
}
// NSBlockOperation執(zhí)行方式
- (void)operationManage{
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
//downloadImage 任務(wù)
}];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
// 同一時間最多開啟的線程數(shù)
operationQueue.maxConcurrentOperationCount = 6;
[operationQueue addOperation:blockOperation];
}
- (void)operationManage{
/*
* 多任務(wù)隊列添加依賴--串行執(zhí)行
* 一般多任務(wù)隊列默認是并行執(zhí)行,添加依賴可按依賴條件順序執(zhí)行
*/
NSBlockOperation *downloadImgPng = [NSBlockOperation blockOperationWithBlock:^{
//downloadImage 任務(wù)
NSLog(@"png -- 當前線程%@",[NSThread currentThread]);
}];
NSBlockOperation *downloadImgJpg = [NSBlockOperation blockOperationWithBlock:^{
//downloadImage 任務(wù)
NSLog(@"jpg -- 當前線程%@",[NSThread currentThread]);
}];
[downloadImgJpg addDependency:downloadImgPng];
NSBlockOperation *downloadImgPdf = [NSBlockOperation blockOperationWithBlock:^{
//downloadImage 任務(wù)
NSLog(@"pdf -- 當前線程%@",[NSThread currentThread]);
}];
[downloadImgPdf addDependency:downloadImgJpg];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
// 同一時間最多開啟的線程數(shù)
operationQueue.maxConcurrentOperationCount = 6;
[operationQueue addOperations:@[downloadImgPng,downloadImgJpg,downloadImgPdf] waitUntilFinished:NO];
}
NSOperation
對象執(zhí)行狀態(tài)監(jiān)聽
/*
此屬性指定應(yīng)用于添加到隊列中的操作對象的服務(wù)級別。如果操作對象具有顯式的服務(wù)水平集,則使用該值。此屬性的默認值取決于您創(chuàng)建隊列的方式。自己創(chuàng)建的隊列,默認值是NSOperationQualityOfServiceBackground。為隊列的mainqueue方法返回,默認值是nsoperationqualityofserviceuserinteractive和不能改變的。
服務(wù)級別影響給定操作對象訪問系統(tǒng)資源的優(yōu)先級,如CPU時間、網(wǎng)絡(luò)資源、磁盤資源等。具有較高服務(wù)質(zhì)量級別的操作在系統(tǒng)資源上被賦予更大的優(yōu)先權(quán),以便它們能更快地執(zhí)行任務(wù)。您使用服務(wù)級別確保響應(yīng)顯式用戶請求的操作優(yōu)先于不重要的工作。
*/
@property NSQualityOfService qualityOfService;
typedef enum NSQualityOfService : NSInteger {
NSQualityOfServiceUserInteractive = 0x21,
NSQualityOfServiceUserInitiated = 0x19,
NSQualityOfServiceUtility = 0x11,
NSQualityOfServiceBackground = 0x09,
NSQualityOfServiceDefault = -1
} NSQualityOfService;
NSQualityOfServiceUserInteractive
用于直接提供交互式UI的工作。例如,處理控件事件或繪制到屏幕上。
NSQualityOfServiceUserInitiated
用于執(zhí)行用戶明確要求的工作,必須立即提交結(jié)果,以便進一步進行用戶交互。例如,在用戶在郵件列表中選擇郵件后,加載電子郵件。
NSQualityOfServiceUtility
用于執(zhí)行用戶不太可能立即等待結(jié)果的工作。這項工作可能是用戶要求的,也可能是自動啟動的,并且經(jīng)常使用非模態(tài)進度指示器在用戶可見的時間尺度上運行。例如,周期性內(nèi)容更新或大容量文件操作,如媒體導入。
NSQualityOfServiceBackground
用于非用戶發(fā)起或可見的工作。一般來說,用戶不知道這項工作甚至正在發(fā)生。例如,預取內(nèi)容,搜索索引、備份或同步與外部系統(tǒng)的數(shù)據(jù)。
NSQualityOfServiceDefault
指示沒有明確的服務(wù)質(zhì)量信息。只要有可能,適當?shù)姆?wù)質(zhì)量由可用的來源決定。否則,選擇的可能是NSQualityOfServiceUserInteractive
和NSQualityOfServiceUtility
之間服務(wù)水平的任意一種。
/*
此屬性包含操作的相對優(yōu)先級。這個值是用來影響其中的操作和執(zhí)行順序出列。
官方建議:為了確定優(yōu)先級,應(yīng)該始終使用這些常量(而不是定義的值)。
*/
@property NSOperationQueuePriority queuePriority;
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
以上兩個屬性通過設(shè)置合適的值,能讓資源利用更加合理。其他的API可查看官方文檔使用,特別注意的是- (void)waitUntilFinished;
這個接口要慎用,該接口絕不能在主線程調(diào)用,會產(chǎn)生死鎖卡死主線程。一般來講用- (void)addDependency:(NSOperation *)op;
依賴API就夠了,簡單易用。
- (void)waitUntilFinished;
接口的文檔解釋:
An operation object must never call this method on itself and should avoid calling it on any operations submitted to the same operation queue as itself. Doing so can cause the operation to deadlock. Instead, other parts of your app may call this method as needed to prevent other tasks from completing until the target operation object finishes. It is generally safe to call this method on an operation that is in a different operation queue, although it is still possible to create deadlocks if each operation waits on the other.
A typical use for this method would be to call it from the code that created the operation in the first place. After submitting the operation to a queue, you would call this method to wait until that operation finished executing.
翻譯:操作對象絕不能自己調(diào)用這個方法,應(yīng)該避免在提交給同一操作隊列的任何操作中調(diào)用它。這樣做可能導致操作死鎖。相反,應(yīng)用程序的其他部分可以根據(jù)需要調(diào)用此方法,以防止其他任務(wù)完成,直到目標操作對象完成為止。一般來說,在不同的操作隊列中調(diào)用這種方法是安全的,但如果每個操作都等待另一個操作,仍然有可能造成死鎖。
這種方法的一個典型用途是首先從創(chuàng)建操作的代碼調(diào)用它。在向隊列提交操作之后,您將調(diào)用此方法等待該操作完成執(zhí)行。
GCD的主要API使用
-
GCD的實現(xiàn)步驟
GCD實現(xiàn)多線程的步驟主要有2步:
1>創(chuàng)建隊列
2>添加執(zhí)行任務(wù)到隊列中
也可以是1步,添加執(zhí)行任務(wù)到系統(tǒng)標準隊列
沒錯,就是這么簡單易用!以下是代碼片段:
// 創(chuàng)建隊列 ,手動創(chuàng)建的隊列需要做釋放處理,因為Dispatch Queue并沒有被作為OC對象處理
/*
* 1.創(chuàng)建串行隊列
* 串行隊列有兩種創(chuàng)建方式
*/
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.mytest", NULL);
// 釋放隊列
dispatch_release(serialQueue);
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.mytest", DISPATCH_QUEUE_SERIAL);
// 釋放隊列
dispatch_release(queue);
// 2.創(chuàng)建并行隊列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.gcd.mytest", DISPATCH_QUEUE_CONCURRENT);
// 3.添加同步任務(wù)到隊列中
dispatch_sync(concurrentQueue, ^{
// 執(zhí)行任務(wù)
});
// 4.添加異步任務(wù)到隊列中
dispatch_async(concurrentQueue, ^{
// 執(zhí)行任務(wù)
});
// 5.釋放隊列( iOS 6.0 or Mac OS X 10.8 以上系統(tǒng)可以管理GCD對象無需手動釋放)
dispatch_release(concurrentQueue);
// 一步搞定
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執(zhí)行操作
});
GCD的隊列主要有兩種Serial Dispatch Queue
和Concurrent Dispatch Queue
。前者只有一個線程,后者根據(jù)任務(wù)量,可以開啟多條線程。當然多個Serial Dispatch Queue
是可以并行執(zhí)行的。
-
Main Dispatch Queue / Global Dispatch Queue
Main Dispatch Queue
和Global Dispatch Queue
是系統(tǒng)提供的標準Dispatch Queue
,這兩個標準隊列還有一個共同的優(yōu)點,那就是相比于手動創(chuàng)建的隊列,這兩個隊列不需要開發(fā)者做隊列釋放的操作,因為這兩個隊列對應(yīng)用程序而言是全局的,詳細可查看官方文檔。
Main Dispatch Queue
可能你已經(jīng)猜到了,沒錯,它就是主線程隊列,追加到Main Dispatch Queue
的處理在主線程的RunLoop
中執(zhí)行,一些需要更新UI界面的操作可以放到這個線程中執(zhí)行。
Global Dispatch Queue
是Concurrent Dispatch Queue
類型的隊列。所以我們一般是不用逐個生成Concurrent Dispatch Queue
隊列的,只要使用全局隊列Global Dispatch Queue
就可以了。
// 主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 全局隊列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
創(chuàng)建全局隊列的APIdispatch_get_global_queue(long identifier, unsigned long flags);
需要傳兩個參數(shù),第一個是優(yōu)先級,根據(jù)實際處理內(nèi)容選擇合適的優(yōu)先級。第二個官方文檔稱是為將來使用預留的,一般傳數(shù)字0即可。
Global Dispatch Queue
有4個執(zhí)行優(yōu)先級。
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺優(yōu)先級
-
dispatch_sync / dispatch_asycn
dispatch_asycn
是異步處理函數(shù),該函數(shù)不會等待任務(wù)執(zhí)行完,不會一直占用當前線程。
dispatch_sync
是同步處理函數(shù),在該函數(shù)中執(zhí)行的任務(wù)不執(zhí)行完,該函數(shù)會一直在當前線程等待。也可以說這個函數(shù)是簡化版的dispatch_group_wait
函數(shù)。
dispatch_sync
要慎用,因為使用不當就會引起死鎖。
比如在主線程調(diào)用:
dispatch_queue_t mianQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
dispatch_sync(mainQueue, ^{
NSLog(@"Hello World");
});
});
在Serial Diapatch Queue
中調(diào)用也是一樣
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.mytest", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
dispatch_sync(queue, ^{
NSLog(@"Hello World");
});
});
死鎖的原因很明顯就是兩個操作在同一個線程里互相等待對方執(zhí)行完,一直互相等待,誰也不執(zhí)行。
-
dispatch_set_target_queue
手動創(chuàng)建的隊列可以通過dispatch_set_target_queue
設(shè)定執(zhí)行的優(yōu)先級。將Dispatch Queue
指定為dispatch_set_target_queue
的函數(shù)參數(shù),不僅可以變更Dispatch Queue
的執(zhí)行優(yōu)先級,還可以作為執(zhí)行階層。
比如,將一個普通的Serial Diapatch Queue
設(shè)定為與“后臺優(yōu)先級全局隊列”一樣的優(yōu)先級和階層,那么在執(zhí)行時,它的執(zhí)行優(yōu)先級將高于其他普通的Serial Diapatch Queue
和Concunrrent Diapatch Queue
,它的階層也要比普通的Serial Diapatch Queue
和Concunrrent Diapatch Queue
高,當它與“后臺優(yōu)先級全局隊列”階層一樣時意味著,如果它在執(zhí)行,其他普通的Serial Diapatch Queue
和Concunrrent Diapatch Queue
都不能和它并行執(zhí)行,必須等它執(zhí)行完才能執(zhí)行。
// 創(chuàng)建串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.mytest", NULL);
// 創(chuàng)建并行隊列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.gcd.mytest", DISPATCH_QUEUE_CONCURRENT);
/*
* 參照serialQueue的優(yōu)先級設(shè)置目標隊列 即concurrentQueue的優(yōu)先級
* 第一個參數(shù)為要設(shè)置優(yōu)先級的queue,第二個參數(shù)是參照物,既將第一個queue的優(yōu)先級和第二個queue的優(yōu)先級設(shè)置一樣。
*/
dispatch_set_target_queue(concurrentQueue, serialQueue);
-
dispatch_after
dispatch_after
用于延時處理,需要注意的是并不是dispatch_after
在延時指定時間后執(zhí)行,而是在指定時間把任務(wù)添加到隊列中,相當于加了一個計時器,時間到了就把任務(wù)添加到隊列中了。
栗子:
延時3秒打印Hello Word
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{NSLog(@"Hello World");});
dispatch_time
的第一個參數(shù)是起始時間可傳:DISPATCH_TIME_NOW
和 DISPATCH_TIME_FOREVER
。
DISPATCH_TIME_NOW
: 表示從現(xiàn)在開始 DISPATCH_TIME_FOREVER
:表示持續(xù)等待,稍后會用到。
第二個參數(shù)是持續(xù)多久,"ull"
是C語言的數(shù)值字面量,是顯示表明類型時使用的字符串(表示"unsight long long")為了精確時間寫上"ull"
。NSEC_PER_SEC
是秒時間單位的一種,表示納秒級精確的一秒。
NSEC:納秒。
USEC:微妙。
SEC:秒
PER:每
#define NSEC_PER_SEC 1000000000ull // 每秒有多少納秒
#define NSEC_PER_MSEC 1000000ull // 每毫秒有多少納秒
#define USEC_PER_SEC 1000000ull // 每秒有多少毫秒
#define NSEC_PER_USEC 1000ull // 每毫秒有多少納秒
-
Dispatch Group
有時我們想等添加到隊列中的所有任務(wù)都執(zhí)行完再執(zhí)行結(jié)束處理。Serial Dispatch Queue
好說,本來就是串行的。但是Concurrent Dispatch Queue
就不行了,異步的我們根本不知道哪個是最后執(zhí)行完的。這個時候就可以用到Dispatch Group
了。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{NSLog(@"block 0");});
dispatch_group_async(group, queue, ^{NSLog(@"block 1");});
dispatch_group_async(group, queue, ^{NSLog(@"block 2");});
/*
* 無論向什么樣的Dispatch Queue中添加任務(wù),使用Dispatch Group都可以監(jiān)聽這個任務(wù)的執(zhí)行結(jié)束
* 所有任務(wù)結(jié)束時,會執(zhí)行dispatch_group_notify函數(shù)的block
*/
dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done");});
// iOS 6.0 or Mac OS X 10.8 以上系統(tǒng)可以管理GCD對象無需手動釋放
dispatch_release(group);
用dispatch_group_wait
也可以達到同樣的效果,不過前者更簡潔,所以建議用dispatch_group_notify
,下邊是dispatch_group_wait
的實現(xiàn)。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{NSLog(@"block 0");});
dispatch_group_async(group, queue, ^{NSLog(@"block 1");});
dispatch_group_async(group, queue, ^{NSLog(@"block 2");});
dispatch_time_t time = dispatch_time(DISPATCH_TIME_FOREVER, 1ull*NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
/*
指定DISPATCH_TIME_NOW,則不用任何等待,即可判斷Dispatch Group中的處理是否執(zhí)行結(jié)束
(在主線程的Runloop的每次循環(huán)中可檢查執(zhí)行是否結(jié)束,從而不耗費多余的等待時間)
*/
// long result = dispatch_group_wait(group, DISPATCH_TIME_NOW);
if (result == 0) {
// group的全部任務(wù)執(zhí)行完畢
}
else {
// group的某個任務(wù)還在執(zhí)行中
}
// iOS 6.0 or Mac OS X 10.8 以上系統(tǒng)可以管理GCD對象無需手動釋放
// dispatch_release(group);
-
Dispatch Semaphore 信號量
GCD信號量的用法主要有兩種,一種是監(jiān)聽1個異步執(zhí)行結(jié)果,另外一種是控制線程并發(fā)數(shù)。
信號量的函數(shù)有3個:
- 創(chuàng)建信號量,傳入的value要大于等于0
dispatch_semaphore_create(long value);
- 增加一個信號量
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
- 減少1個信號量,并且信號量為0則等待,等待時間取決于
timeout
時間參數(shù)
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
接下來看代碼:
- 信號量監(jiān)聽1個異步執(zhí)行結(jié)果
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);// 信號量為0
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"run task 1");
sleep(1);
NSLog(@"complete task 1");
// 信號量+1
dispatch_semaphore_signal(semaphore);
});
// 若信號量為0則一直等待
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"繼續(xù)執(zhí)行函數(shù)");
- 打印結(jié)果
2017-12-19 11:35:31.731268+0800 多線程[3482:317136] run task 1
2017-12-19 11:35:32.736671+0800 多線程[3482:317136] complete task 1
2017-12-19 11:35:32.737201+0800 多線程[3482:316871] 繼續(xù)執(zhí)行函數(shù)
- 信號量控制線程數(shù)
// 信號量為2,相當于線程最大并發(fā)數(shù)為2
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 若信號量為0則一直等待,不為0則信號量-1并繼續(xù)執(zhí)行函數(shù)
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 1");
sleep(1);
NSLog(@"complete task 1");
dispatch_semaphore_signal(semaphore); // 信號量+1
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 2");
sleep(1);
NSLog(@"complete task 2");
dispatch_semaphore_signal(semaphore);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 3");
sleep(1);
NSLog(@"complete task 3");
dispatch_semaphore_signal(semaphore);
});
- 打印結(jié)果
/*
1.信號量初始值為2(即線程最大并發(fā)數(shù)為2);
2.任務(wù)1執(zhí)行信號量-1,任務(wù)2執(zhí)行信號量-1,此時信號量為0(即兩個線程已經(jīng)有任務(wù)在執(zhí)行,任務(wù)3只能等待);
3.當任務(wù)1或任務(wù)2執(zhí)行完,則信號量+1,信號量不為0(即有一個可用線程,此時任務(wù)3可執(zhí)行)。
*/
2017-12-19 11:41:28.631848+0800 多線程[3542:324794] run task 2
2017-12-19 11:41:28.631849+0800 多線程[3542:324796] run task 1
2017-12-19 11:41:29.636731+0800 多線程[3542:324794] complete task 2
2017-12-19 11:41:29.636758+0800 多線程[3542:324796] complete task 1
2017-12-19 11:41:29.637094+0800 多線程[3542:324795] run task 3
2017-12-19 11:41:30.637980+0800 多線程[3542:324795] complete task 3
關(guān)于信號量和Dispatch Group異步線程的延伸閱讀
-
dispatch_once
dispatch_once
大家比較熟,因為線程安全的單例模式常用到它。
+(instancetype)sharedSingleton{
static id instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
還可以用@synchronized()
這個互斥鎖實現(xiàn)單例模式:
+(instancetype)sharedSingleton{
static id instance = nil;
@synchronized (self) {
if (!instance) {
instance = [[self alloc] init];
}
}
return instance;
}
-
dispatch_barrier_async
在訪問數(shù)據(jù)庫或文件時,為避免數(shù)據(jù)競爭,前面講到可以使用Serial Dispatch Queue
。但是其實如果是讀取和讀取并行執(zhí)行是不會引起數(shù)據(jù)競爭的,如果能把這部分操作拆分出來,那無疑會提高訪問效率。dispatch_barrier_async
配合手動創(chuàng)建的Concurrent Dispatch Queue
就可以幫我們做到。在執(zhí)行dispatch_barrier_async
時,它會等正在執(zhí)行的任務(wù)執(zhí)行完開始,當它結(jié)束后其他任務(wù)才會再開始執(zhí)行。在SDWebImage框架中也有用到它和它的同步函數(shù)dispatch_barrier_sync
。
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.mytest", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 執(zhí)行讀取操作
});
dispatch_async(queue, ^{
// 執(zhí)行讀取操作
});
dispatch_barrier_async(queue, ^{
// 執(zhí)行寫入操作
});
dispatch_async(queue, ^{
// 執(zhí)行讀取操作
});
dispatch_async(queue, ^{
// 執(zhí)行讀取操作
});
-
dispatch_suspend / dospatch_resume
在隊列大量任務(wù)執(zhí)行時,需要臨時掛起隊列時調(diào)用dispatch_suspend
dispatch_suspend(queue)
掛起隊列
dospatch_resume (queue)
恢復隊列
GCD還提供可多線程讀取同一個大型文件的APIdispatch I/O
和dispatch Data
,還有其他很多有意思有用的API,大家盡可以去查看官方文檔學習和使用。
參考
- 蘋果官方文檔
- 《現(xiàn)代操作系統(tǒng)》
- 《Objective-C高級編程- iOS與OS X多線程和內(nèi)存管理》