0.前言
0.什么是GCD?
GCD全程為Grand Central Dispatch,是異步執(zhí)行的技術(shù)之一。一般將應(yīng)用程序中記述的線程管理代碼在系統(tǒng)級實現(xiàn)。開發(fā)者只用定義向執(zhí)行的任務(wù),并追加到適當(dāng)?shù)腄ispatch Queue中就完事兒了,GCD能自動生成必要的線程并執(zhí)行任務(wù)。
1.單線程
CPU一次只能執(zhí)行一個命令 ,不能一心兩用,同時執(zhí)行兩個分開的并列命令,因此通過CPU執(zhí)行命令就像是一條五分叉的路,及時地址分散,看似凌亂,實則也只有一條路徑。這就是線程。
2.上下文切換
macOS和iOS中的核心XNU內(nèi)核在發(fā)生操作系統(tǒng)事件時,會切換執(zhí)行路徑,執(zhí)行中路徑的狀態(tài),會被保存到各自路徑專用的內(nèi)存塊中,從切換目標(biāo)路徑專用的內(nèi)存塊中,復(fù)原CPU寄存器等信息,繼續(xù)執(zhí)行切換路徑的CPU命令列。這就是上下文切換。
3.為什么長時間的處理不放在主線程執(zhí)行?
程序在啟動時,最先執(zhí)行的線程是主線程,用來描繪用戶界面、處理屏幕觸摸的事件。如果主線程進行長時間的處理,機會導(dǎo)致主線程阻塞,妨礙主線程中RunLoop主循環(huán)的執(zhí)行,導(dǎo)致不能更新用戶界面、應(yīng)用畫面長時間停滯等問題。
1.GCD的API
1. DIspatch Queue
名詞解釋
官方對GCD的說明:
開發(fā)者要做的只是定義想執(zhí)行的任務(wù),并追加到適當(dāng)?shù)腄ispatch Queue中。
源碼的表示就是:
dispatch_sync(queue, ^{
//your work here;
});
[4] → | [3] [2] [1] | → [0]
追加 Dispatch Queue 已處理
Dispatch Queue遵循FIFO原則,先進先出。
Dispatch Queue的種類
- Serial Dispatch Queue :串行,一個線程
- Concurrent Dispatch Queue :并行,多線程
2.如何得到Dispatch Queue
1.通過CGD的API生成
以下代碼生成了Serial Dispatch Queue
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.MyGCDSerialDispatchQueue", NULL);
以下代碼生成了Concurrent Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.gcd.myGCDConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
關(guān)于Serial Dispatch Queue的個數(shù)
一個Serial Dispatch Queue同時只能追加一個處理。
多個Serial DIspatch Queue各追加處理,并同時執(zhí)行,就變成并行執(zhí)行。
注意,一旦生成1個Serial DIspatch Queue,系統(tǒng)就對一個Queue生成并使用一個線程,如果生成2000個,那就生成2000個線程。
因此,只是在為了避免多個線程更新相同資源導(dǎo)致的數(shù)據(jù)競爭時使用Serial DIspatch Queue
關(guān)于dispatch_queue_create方法
dispatch_queue_create(const char * _Nullable label, dispatch_queue_attr_t _Nullable attr)
第一個參數(shù)為queue的名稱,推薦輸入逆序全程域名,崩潰時會出現(xiàn)在log中。
第二個參數(shù)為queue的類型,如果輸入NULL則為Serial,如果輸入DISPATCH_QUEUE_CONCURRENT則為Concurrent。
另:iOS6之后,DIspatch Queue也加入了ARC,如果手動設(shè)置Retain和Release,會報錯。
將Block內(nèi)的執(zhí)行追加到Queue中
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.gcd.myGCDConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"v-tech is the BEST!");
});
2.獲取系統(tǒng)標(biāo)準(zhǔn)提供的Dispatch Queue
系統(tǒng)提供的Dispatch Queue有:
- Main Dispatch Queue
- Global Dispatch Queue
Main Dispatch Queue是在主線程執(zhí)行的Dispatch Queue,只有一個,是Serial Dispatch Queue。追加到Main Dispatch Queue的處理在主線程的RunLoop中執(zhí)行,因此必須將用戶界面更新等一些必須在主線程中執(zhí)行的處理追加到Main Dispatch Queue中。
Global Dispatch Queue是所有程序都可以使用的Concurrent Dispatch Queue。沒有必要通過dispatch_queue_create逐個生成Concurrent Dispatch Queue,直接獲取Global Dispatch Queue就行了。
獲取Main Dispatch Queue和 Global Dispatch Queue的方法
//main
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
//global
dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(long identifier, unsigned long flags);
//global example
dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
獲取Main Dispatch Queue的方法只有一個。
獲取Global的方法里有兩個變量,第一個是設(shè)定優(yōu)先級,有以下四個可以選擇:
第二個參數(shù)傳入0即可。
文檔關(guān)于dispatch_get_global_queue的介紹:
/*!
* @function dispatch_get_global_queue
*
* @abstract
* Returns a well-known global concurrent queue of a given quality of service
* class.
*
* @discussion
* The well-known global concurrent queues may not be modified. Calls to
* dispatch_suspend(), dispatch_resume(), dispatch_set_context(), etc., will
* have no effect when used with queues returned by this function.
*
* @param identifier
* A quality of service class defined in qos_class_t or a priority defined in
* dispatch_queue_priority_t.
*
* It is recommended to use quality of service class values to identify the
* well-known global concurrent queues:
* - QOS_CLASS_USER_INTERACTIVE
* - QOS_CLASS_USER_INITIATED
* - QOS_CLASS_DEFAULT
* - QOS_CLASS_UTILITY
* - QOS_CLASS_BACKGROUND
*
* The global concurrent queues may still be identified by their priority,
* which map to the following QOS classes:
* - DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED
* - DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT
* - DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY
* - DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
*
* @param flags
* Reserved for future use. Passing any value other than zero may result in
* a NULL return value.
*
* @result
* Returns the requested global queue or NULL if the requested global queue
* does not exist.
*/
3.延遲處理
場景:在3秒后將指定的Block追加到Main Dispatch Queue
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"wait at least 3 second");
});
注意,代碼指的不是3秒后執(zhí)行Block內(nèi)的處理,而是在3秒后將執(zhí)行追加到Queue中執(zhí)行。
因為Main Dispatch Queue 在主線程的RunLoop中執(zhí)行,所以在比如每隔1/60秒執(zhí)行的RunLoop中,Block最快在3秒后執(zhí)行,最慢在3秒+1/60秒后執(zhí)行,并且在Main Dispatch Queue有大量處理追加或者主線程的處理本身有延遲,這個時間會更長。
dispatch_time方法解析
dispatch_time(dispatch_time_t when, int64_t delta)
第一個參數(shù)是時間點,DISPATCH_TIME_NOW意思是現(xiàn)在。
第二個參數(shù)是距離時間點的長度,也就是多少時間之后追加到Queue中,格式如下:
3ull * NSEC_PER_SEC
/*
* 3ull * NSEC_PER_SEC 表示3秒
*
* ull = unsigned long long
*
* #define NSEC_PER_SEC 1000000000ull
* #define NSEC_PER_MSEC 1000000ull
* #define USEC_PER_SEC 1000000ull
* #define NSEC_PER_USEC 1000ull
*
*/
4.等待所有任務(wù)完成后執(zhí)行其他處理
1.Dispatch Group
場景:在追加到Dispatch Queue中的多個處理全部結(jié)束之后執(zhí)行其他處理。
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(@"blk0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"other method");
});
dispatch_group_create()生成dispatch_group_t類的Dispatch Group。
dispatch_group_async與dispatch——async相同,都是追加Block到指定的Dispatch Queue中。不同的是,指定的Block屬于指定的Dispatch Group。
在追加到Dispatch Group中的處理全部執(zhí)行結(jié)束后,該源代碼中使用的dispatch_group_notify函數(shù)會將執(zhí)行的Block追加到Dispatch Queue中,將第一個參數(shù)指定為要監(jiān)視的Dispatch Group。在追加到改Group的全部處理執(zhí)行解釋后,將第三個參數(shù)的Block追加到第二個參數(shù)的Dispatch Queue中。在dispatch_group_notify中不管制定什么樣的Dispatch Queue,屬于Dispatch Group的全部處理在追加指定的Block時已經(jīng)結(jié)束。
dispatch_group_async的介紹
/*!
* @function dispatch_group_async
*
* @abstract
* Submits a block to a dispatch queue and associates the block with the given
* dispatch group.
*
* @discussion
* Submits a block to a dispatch queue and associates the block with the given
* dispatch group. The dispatch group may be used to wait for the completion
* of the blocks it references.
*
* @param group
* A dispatch group to associate with the submitted block.
* The result of passing NULL in this parameter is undefined.
*
* @param queue
* The dispatch queue to which the block will be submitted for asynchronous
* invocation.
*
* @param block
* The block to perform asynchronously.
*/
2.Dispatch Group Wait
在Group中也可以使用dispatch_group_wait函數(shù)僅等待全部處理執(zhí)行結(jié)束。
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
forever的意思是永遠(yuǎn)等待,只要Group里面的處理尚未執(zhí)行,就會一直等待。
如同dispatch_after一樣,制定等待間隔為1秒的處理如下:
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(@"blk0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk2");
});
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if(result == 0){
//屬于Dispatch Group的處理全部執(zhí)行結(jié)束
NSLog(@"done");
}else{
//屬于Dispatch Group的某一個處理還在進行 或者 超時了
NSLog(@"doing");
}
其中,dispatch_group_wait(group, time)中的time是time-ou時間,如果到了time制定的時間還沒有完成處理,就直接返回非零值,表示超時了。
如果不想等待,直接判定,可以把時間設(shè)定為DISPATCH_TIME_NOW。如果要一直等待,就設(shè)定為DISPATCH_TIME_FOREVER。
3.notify和wait
在主線程RunLoop的每次循環(huán)中,可檢查執(zhí)行是否結(jié)束,從而不耗費多余的等待時間,雖然這樣也可以,但一般這種情況下,還是推薦使用notify,函數(shù)追加結(jié)束處理到Main Dispatch Queue中們可以簡化源代碼。
5.處理寫入處理的數(shù)據(jù)競爭
寫入處理不能并行執(zhí)行,會引發(fā)數(shù)據(jù)競爭,但是讀取處理可以,因此,為了高效訪問,讀取處理可以追加到Concurrent Dispatch Queue,寫入處理可以在任一個讀取處理沒有執(zhí)行的狀態(tài)下,追加到Serial Dispatch Queue中(寫入處理結(jié)束之前,讀取處理不可執(zhí)行)。
使用dispatch_barrier_async函數(shù)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"read 0");
});
dispatch_async(queue, ^{
NSLog(@"read 1");
});
dispatch_async(queue, ^{
NSLog(@"read 2");
});
dispatch_async(queue, ^{
NSLog(@"read 3");
});
/*
* 寫入處理
*/
dispatch_barrier_async(queue, ^{
NSLog(@"write");
});
dispatch_async(queue, ^{
NSLog(@"read 4");
});
dispatch_async(queue, ^{
NSLog(@"read 5");
});
dispatch_async(queue, ^{
NSLog(@"read 6");
});
dispatch_async(queue, ^{
NSLog(@"read 7");
});