什么是線程?####
當(dāng)一個應(yīng)用程序安裝到 Mac 或 iPhone上的時候, Mac、iPhone 的操作系統(tǒng) OSX, iOS 會根據(jù)用戶的姿勢啟動該應(yīng)用程序后,一個一個地執(zhí)行CPU命令行,先執(zhí)行一個命令,再接著執(zhí)行另一個命令,這樣不斷的循環(huán)下去。
CPU 一次只能執(zhí)行一個命令, 不能執(zhí)行某處分開的并行的兩個命令,因此通過 CPU 執(zhí)行的 CPU 命令就好比一份無分叉的大道,其執(zhí)行不會出現(xiàn)分歧。所以,1個CPU 執(zhí)行的CPU命令列為一條無分叉路經(jīng)即為線程;
但,這種無分叉路經(jīng)不只一條,存在多條時候,即為多線程,1 個 CPU 核執(zhí)行多條不同路徑上的不同命令。 雖然CPU相關(guān)技術(shù)很多, 但基本上1個CPU核一次能夠執(zhí)行的CPU命令始終為1。 OS X和 iOS 的內(nèi)核在發(fā)生操作系統(tǒng)事件的時候,會切換執(zhí)行路經(jīng),執(zhí)行中路經(jīng)的狀態(tài),例如CPU的寄存器等信息保存到各自路經(jīng)專用的內(nèi)存中,從切換目標(biāo)路經(jīng)專用的內(nèi)存中復(fù)原CPU寄存器等信息,繼續(xù)執(zhí)行切換路經(jīng)的 CPU 命令行,這個被稱為`上下文切換`;因而其實線程之間一直進(jìn)行著上下文切換。
多線程容易出現(xiàn)問題,如 多個線程更新相同的資源會導(dǎo)致數(shù)據(jù)的不一致(數(shù)據(jù)競爭)、停止等待時間的線程會導(dǎo)致多個線程相互持續(xù)等待(死鎖)、使用太多線程會消耗大量內(nèi)存等。



即使有問題,也要采用多線程,這樣才能保證應(yīng)用程序的響應(yīng)性能,啟動后, 最先執(zhí)行的線程(主線程)描繪界面、處理觸摸屏幕的時間等等,若是在主線程中進(jìn)行大量的數(shù)據(jù)操作便會阻塞主線程,從而導(dǎo)致沒法再更新用戶界面,出現(xiàn)長時間卡頓。

GCD大大簡化了偏于復(fù)雜的多線程的源代碼。
什么是GCD?####
GCD是異步執(zhí)行任務(wù)的技術(shù)之一, 一般將應(yīng)用程序中記述的線程管理用的代碼在系統(tǒng)級中的實現(xiàn),開發(fā)者只需要定義想執(zhí)行的任務(wù)并追加到適當(dāng)?shù)?Dispatch Queue 中,GCD 就能生成必要的線程并計劃執(zhí)行任務(wù),由于線程管理是作為系統(tǒng)的一部分來實現(xiàn)的,因此可以統(tǒng)一管理,也可執(zhí)行任務(wù),這樣就比以前的線程更有效率。
也就是說,GCD 用我們難以置信的非常簡潔的記述方法,實現(xiàn)了極為負(fù)責(zé)繁瑣的多線程編程,可以說這是一項劃時代的技術(shù)。
dispatch_async(queue, ^{
// 這一行的代碼表示處理在后臺線程執(zhí)行
/*
* 長時間處理的工作
* 如一些復(fù)雜的數(shù)據(jù)處理
*/
dispatch_async(dispatch_get_main_queue(),^{
// 這代碼表示在主線程中執(zhí)行
/*
* 只在主線程可以執(zhí)行的處理
* 例如用戶界面更新
*/
});
});
Disaptch Queue#####
開發(fā)者要做的只是定義想執(zhí)行的任務(wù)并追加到適當(dāng)?shù)腄ispatch Queue中。
就如同:
dispatch_async(queue, ^{
/*
* 想執(zhí)行的任務(wù)
*/
})
使用了 Block 語法“定義想執(zhí)行的任務(wù)”,通過 dispatch_async 函數(shù)“追加”賦值在變量 queue 的 “Dispatch Queue 中”,這樣就可以指定 Block 在另一個線程中執(zhí)行?!啊盌ispatch Queue 按照追加的順序(FIFO)執(zhí)行處理。
在執(zhí)行處理時候,存在兩種 Disparch Queue, 一種是等待執(zhí)行中處理的 Serial Dispatch Queue, 另一種是不等待現(xiàn)在執(zhí)行中處理的 Concurrent Dispatch Queue。
dispatch_async(queue, block0);
dispatch_async(queue, block1);
dispatch_async(queue, block2);
dispatch_async(queue, block3);
dispatch_async(queue, block4);
dispatch_async(queue, block5);
如以上代碼,當(dāng) queue 為 Serial Dispatch Queue 時候,因為要等待現(xiàn)在執(zhí)行中的處理結(jié)果,所以順序為
block0
block1
block2
block3
block4
block5
當(dāng)變量 queue 為 Concurrent Dispatch Queue 時,因為不用等待現(xiàn)在執(zhí)行中的處理結(jié)束,所以執(zhí)行了 block0 不不管其是否執(zhí)行結(jié)束都執(zhí)行 block1, 如此重復(fù)。
iOS 和 OS X 的核心 --XNU 內(nèi)核決定應(yīng)當(dāng)使用的線程數(shù),并只生成所需要的線程執(zhí)行處理,當(dāng)處理結(jié)束,應(yīng)當(dāng)執(zhí)行的處理數(shù)減少時,XNU 內(nèi)核會結(jié)束不再需要的線程。
dispatch_queue_create#####
通過 Dispatch_queue_create 函數(shù)生成 Dispatch Queue。
dispatch_queue_t mySyrialQueue = dispatch_queue_create("com.queue.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(mySyrialQueue, ^{
NSLog(@"mySyrialQueue block");
});
// 第一個參數(shù)指定 Queue 的名稱,建議使用應(yīng)用程序 ID 這種命名方式, 當(dāng)然也可以設(shè)置為 NULL;
// 第二個參數(shù)指定 DISPATCH_QUEUE_SERIAL 或 NULL 表示 生成 Serial Dispatch Queue
雖然 Serial Dispatch Queue 同時只能追加一個處理,但當(dāng)生成多個 Serial Dispatch Queue 時,各個 Serial Dispatch Queue 將并行執(zhí)行,即同時執(zhí)行多個。系統(tǒng)對于一個 Serial Dispatch Queue 就只能生成并使用一個線程, 如果生成2000個 Serial Dispatch Queue, 那么就可以生成2000多個線程。如果過多的使用線程,就會消耗大量的內(nèi)存,引起大量的上下文切換,大幅度降低系統(tǒng)的響應(yīng)性能。
所以, 盡量在“避免多個線程更新相同資源導(dǎo)致數(shù)據(jù)競爭時”使用 Serial Dispatch Queue

dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.queue.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myConcurrentQueue, ^{
NSLog(@"myConcurrentQueue block");
});
// 第一個參數(shù)指定 Queue 的名稱,建議使用應(yīng)用程序 ID 這種命名方式, 當(dāng)然也可以設(shè)置為 NULL;
// 第二個參數(shù)指定 DISPATCH_QUEUE_CONCURRENT 生成 Concurrent Dispatch Queue
Main Dispatch Queue/Global Dispatch Queue
除了創(chuàng)建線程之外, 系統(tǒng)還提供了標(biāo)準(zhǔn)的 Dispatch Queue, 就是 Main Dispatch Queue 和 Global Dispatch Queue。
Main Dispatch Queue
主線程, 是一個 Serial Dispatch Queue,追加到主線程的處理在主線程的 RunLoop 中執(zhí)行, 由于在主線程中執(zhí)行,因此要將用戶界面的界面更新等一些必須在主線程中執(zhí)行的處理追加到 Main Dispatch Queue 使用。-
Global Dispatch Queue
Global Dispatch Queue 是所有的應(yīng)用程序都能使用的 Concurrent Dispatch Queue,沒有必要通過 dispatch_queue_create 函數(shù)來逐個生成 Concurrent Dispatch Queue。只要獲取即可。它包含四個執(zhí)行優(yōu)先級。- 高優(yōu)先級(High Priority)
- 默認(rèn)優(yōu)先級(Default Priority)
- 低優(yōu)先級(Low priority)
- 后臺優(yōu)先級(Background Poriority)
在向 Global Dispatch Queue 追加處理的時候,應(yīng)選擇與處理內(nèi)容對應(yīng)的執(zhí)行優(yōu)先級的 Global Dispatch Queue。但是并不能保證實時性,因此優(yōu)先級只是大致的判斷。
// Main Dispatch Queue 的獲取方法
dispatch_queue_t mainDisatchQueue = dispatch_get_main_queue();
// Global Dispatch Queue(高優(yōu)先級)的獲取方法
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
DISPATCH_QUEUE_PRIORITY_HIGH // 高優(yōu)先級
DISPATCH_QUEUE_PRIORITY_LOW // 低優(yōu)先級
DISPATCH_QUEUE_PRIORITY_DEFAULT // 默認(rèn)優(yōu)先級
DISPATCH_QUEUE_PRIORITY_BACKGROUND // 后臺優(yōu)先級
// 使用 Main Dispatch Queue 和 Global Dispatch Queue 源碼
/*
* 在默認(rèn)的優(yōu)先級的 Global Dispatch Queue 中執(zhí)行 block
*/
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
// 可并行執(zhí)行的處理
// 在 main 中block
dispatch_async(dispatch_get_main_queue(), ^{
// 在 主線程中 執(zhí)行 Block
});
});
dispatch_after#####
- 在指定時間后執(zhí)行處理的情況,可以使用 dispatch_after, 需要注意的是 dispatch_after 并不是在指定時間后執(zhí)行處理,而是在指定時間追加處理到 Dispatch Queue。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_MSEC);
// ull為C語言數(shù)值字面量。
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"waited at least three secons");
});
// 第一個參數(shù) 指定時間用的 dispatch_time_t 類型的值 使用 dispatch_time 函數(shù)或者 dispatch_walltime 函數(shù)構(gòu)成
// dispatch_time_t 表示相對時間, dispatch_walltime 表示絕對時間
以上代碼,因為 Main Dispatch Queue 在主線程的 RunLoop 中執(zhí)行,所以必入每隔 1/60 秒執(zhí)行的 RunLoop 中, Block 最快在3秒后執(zhí)行,最慢也在3+1/60秒后執(zhí)行,
- dispatch_time_t 函數(shù)
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_MSEC);
函數(shù)獲取從第一個參數(shù) 制定的時間開始到第二個參數(shù)指定的毫微秒單位時間后的時間。第一個參數(shù)經(jīng)常使用的值是 `DISPATCH_TIME_NOW` 表示現(xiàn)在的時間。 "ull"表示 C 語言的數(shù)字面量(表示“unsigned long long”)
NSEC_PER_MSEC 表示以毫秒為單位
NSEC_PER_SEC 表示為秒
Dispatch Group#####
在追加到 Dispatch Queue 中的多個處理全部結(jié)束后限制性結(jié)束處理, 這種情況經(jīng)常出現(xiàn), 當(dāng)為 Serial Dispatch Queue 時候, 只要將想要執(zhí)行的處理全部追加到 Serial Dispatch Queue 中并在最后追加結(jié)束處理即可實現(xiàn),但是要使用 Concurrent Dispatch Queue 時或同事使用多個 Dispatch Queue 時候,源碼就會變得頗為復(fù)雜。
此種情況適合使用 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(@"block1");});
dispatch_group_async(group, queue, ^{NSLog(@"block2");});
dispatch_group_async(group, queue, ^{NSLog(@"block3");});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"done");
});
// 執(zhí)行結(jié)果
block1
block2
block3
done
因為向 Global Dispatch Queue 即 Concurrent Dispatch Queue 追加處理,多個線程并行處理執(zhí)行,所以追加處理的執(zhí)行順序不定,執(zhí)行時候會發(fā)生變化,但是此執(zhí)行結(jié)果的 done 一定是最后輸出的。
無論向什么樣的 Dispatch Queue 中追加處理, 使用 Dispatch Group 都可監(jiān)視這些處理執(zhí)行的結(jié)束,一旦監(jiān)測到所有的處理執(zhí)行結(jié)束, 就可以結(jié)束的處理追加到 Dispatch Queue 中, 這就是使用 Dispatch Group 的原因。
dispatch_group_notify 函數(shù)第一個參數(shù)為指定的要監(jiān)視的 Dispatch Group. 在追加到該 Dispatch Group 的全部處理執(zhí)行結(jié)束后,將第三個參數(shù)的 block 追加第二個參數(shù)的 Dispatch Queue 中, 在 dispatch_group_notify 函數(shù)中不管指定什么樣的 Dispatch Queue。屬于 Dispatch Group 的全部處理在追加到指定的 Block 時都已執(zhí)行結(jié)束。
除了 dispatch_group_async 之外,還可以使用dispatch_group_wait 僅等待全部處理執(zhí)行結(jié)束。
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(@"block1");});
dispatch_group_async(group, queue, ^{NSLog(@"block2");});
dispatch_group_async(group, queue, ^{NSLog(@"block3");});
NSInteger i = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// 如果 i = 0 表示處理結(jié)束, 反之 Group 并沒執(zhí)行完。
第二個參數(shù),為 dispatch_time_t 類型,為等待的時間, 假設(shè)block1里執(zhí)行了復(fù)雜的耗時操作超過一秒, 當(dāng)?shù)诙€參數(shù)為小于1 秒時, 這 `dispatch_group_wait` 返回的 肯定不為0 則表示為 Group 并沒執(zhí)行完, 假設(shè)第二個參數(shù)為 `DISPATCH_TIME_FOREVER` ,意味永久等待,所以只要 Group 的處理尚未執(zhí)行結(jié)束, 就會一直等待, 中途不能取消。 也就 `dispatch_group_wait` 返回的值也一定為0;
dispatch_barrier_async#####
訪問數(shù)據(jù)庫或者文件的時候,如前所述,使用 Serial Dispatch Queue 可避免數(shù)據(jù)競爭的問題。 寫入數(shù)據(jù)處理不可與其他寫入數(shù)據(jù)處理以及包涵讀取處理的其它某些處理并行執(zhí)行,但是如果讀取處理只是與讀取處理并行執(zhí)行, 那么多個并行執(zhí)行就不會發(fā)生問題。
首先,用 dispatch_queue_create 函數(shù)生成 Concurrent Dispatch Queue, 在 dispatch_async 中追加讀取處理。
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.forBarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, block0_for_reading);
dispatch_async(queue, block1_for_reading);
// 進(jìn)行寫入操作
dispatch_async(queue, block0_for_writing);
//
dispatch_async(queue, block2_for_reading);
dispatch_async(queue, block3_for_reading);
若按此代碼執(zhí)行, 在 bock1 與 block2 之間執(zhí)行寫入操作,就可能會在追加寫入之前的處理中讀取到與期待不符合的數(shù)據(jù),還可能因為非法的訪問導(dǎo)致應(yīng)用程序一場。若采用以下操作:
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.forBarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, block0_for_reading);
dispatch_async(queue, block1_for_reading);
// 進(jìn)行寫入操作
dispatch_barrier_async(queue, block0_for_writing);
//
dispatch_async(queue, block2_for_reading);
dispatch_async(queue, block3_for_reading);

則會有效的防止出現(xiàn)的問題,因為 dispatch_barrier_async 函數(shù)會等待追加到 Concurrent Dispatch Queue 上的并執(zhí)行的處理全部結(jié)束之后,再將指定的處理追加到該 Concurrent Dispatch Queue 中,然后由 dispatch_barrier_async 函數(shù)追加的處理執(zhí)行完畢之后,Concurrent Dispatch Queue 的處理又開始執(zhí)行。