在iOS中實現(xiàn)多線程的方案中,GCD是一種很好的方案。GCD在工作時會自動利用更多的處理器核心,以充分利用更強大的機器。GCD是Grand Central Dispatch的簡稱,它是基于C語言的。如果使用GCD,完全由系統(tǒng)管理線程,我們不需要編寫線程代碼。只需定義想要執(zhí)行的任務(wù),然后添加到適當?shù)恼{(diào)度隊列(dispatch queue)。GCD會負責創(chuàng)建線程和調(diào)度你的任務(wù),系統(tǒng)直接提供線程管理。
這里就需要提及GCD的兩個核心概念了,一個是任務(wù)另一個是隊列了,除此之外還有四個重要的名詞術(shù)語:同步、異步、串行和并發(fā)。
簡單的說,任務(wù)就是執(zhí)行的操作(也就是想要做的事情);隊列就是用來存放任務(wù)的容器。同步就是在當前線程中執(zhí)行任務(wù),需要的是馬上執(zhí)行任務(wù);異步就是在另一個線程中執(zhí)行任務(wù);串行就是任務(wù)一個一個按順序的執(zhí)行;并發(fā)就是多個任務(wù)同時執(zhí)行。
1. GCD的基本使用方法
首先要明確GCD的使用步驟大致有三步:
- 創(chuàng)建隊列
- 確定任務(wù)
- 將任務(wù)放到隊列中去執(zhí)行,這里GCD會自動的將任務(wù)從隊列中取出來放到相應(yīng)的線程中去執(zhí)行。
1.1串行同步隊列
a.創(chuàng)建隊列
dispatch_queue_t queue = dispatch_queue_create("同步串行", DISPATCH_QUEUE_SERIAL);
這里的dispatch_queue_t相當于一個基本的數(shù)據(jù)類型,這里指的是隊列類型。
第二個參數(shù):DISPATCH_QUEUE_SERIAL也就是串行,而并發(fā)是DISPATCH_QUEUE_CONCURRENT
b.確定任務(wù)并且放到隊列中
dispatch_sync(queue, ^{
// 這里的代碼就是要執(zhí)行的任務(wù)
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_sync表示的同步。
打印日志如下:
<NSThread: 0x7f93d2d05ee0>{number = 1, name = main}
c.將多個任務(wù)放到隊列中
這里就可以多創(chuàng)建幾個任務(wù)。
for (int i = 0; i < 5; ++i) {
dispatch_sync(queue, ^{
//
[self operation:i];
});
}
NSLog(@"同步串行over");
下面這是控制臺的打印日志,
<NSThread: 0x7f93d2d05ee0>{number = 1, name = main} ~ 0 <NSThread: 0x7f93d2d05ee0>{number = 1, name = main} ~ 1
<NSThread: 0x7f93d2d05ee0>{number = 1, name = main} ~ 2
<NSThread: 0x7f93d2d05ee0>{number = 1, name = main} ~ 3
<NSThread: 0x7f93d2d05ee0>{number = 1, name = main} ~ 4
同步串行over
從上面的打印日志可以看出,任務(wù)始終都是在同一個線程中進行的操作。
所以對于同步串行而言,在主線程中任務(wù)是一個一個按順序執(zhí)行;先執(zhí)行任務(wù),再執(zhí)行“同步串行over”
1.2串行異步
串行異步,首先異步一定會在新的線程中執(zhí)行,所以任務(wù)會在新開辟的子線程中一個個的執(zhí)行任務(wù),愛奇藝的緩存也就是這么做的。
a.創(chuàng)建串行隊列
dispatch_queue_t queue = dispatch_queue_create("串行異步", NULL);
上面講到DISPATCH_QUEUE_SERIAL代表的是串行,其實這里官方文檔中解釋NULL也代表串行:
/*!
* @const DISPATCH_QUEUE_SERIAL
* @discussion A dispatch queue that invokes blocks serially in FIFO order.
*/
#define DISPATCH_QUEUE_SERIAL NULL
b.任務(wù)創(chuàng)建并放到隊列中
for (int i = 0; i < 5; ++i) {
dispatch_async(queue, ^{
[self operation:i];
});
}
NSLog(@"串行異步over again");
這里的dispatch_async表示的異步。
打印日志如下
串行異步over again
NSThread: 0x7fad03d53e30>{number = 2, name = (null)} ~ 0
<NSThread: 0x7fad03d53e30>{number = 2, name = (null)} ~ 1
<NSThread: 0x7fad03d53e30>{number = 2, name = (null)} ~ 2
<NSThread: 0x7fad03d53e30>{number = 2, name = (null)} ~ 3
<NSThread: 0x7fad03d53e30>{number = 2, name = (null)} ~ 4
從打印日志可以看出,串行異步,先執(zhí)行的是串行異步over again,然后再去在子線程中一個一個的執(zhí)行任務(wù)。
1.3并發(fā)同步
并發(fā)是多個任務(wù)可以同時執(zhí)行的,這當然需要是在多線程中的。但是同步又要求的是在同一個線程中進行。
并發(fā)dispatch queue可以同時并行地執(zhí)行多個任務(wù),不過并發(fā)queue仍然按先進先出的順序來啟動任務(wù)。并發(fā)queue會在之前的任務(wù)完成之前就出列下一個任務(wù)并開始執(zhí)行。并發(fā)queue同時執(zhí)行的任務(wù)數(shù)量會根據(jù)應(yīng)用和系統(tǒng)動態(tài)變化,各種因素包括:可用核數(shù)量、其它進程正在執(zhí)行的工作數(shù)量、其它串行dispatch queue中優(yōu)先任務(wù)的數(shù)量等。
a.創(chuàng)建隊列
dispatch_queue_t queue = dispatch_queue_create("并發(fā)同步", DISPATCH_QUEUE_CONCURRENT);
b.將任務(wù)放到隊列中
for (int i = 0; i < 5; ++i) {
dispatch_sync(queue, ^{
//
[self operation:i];
});
}
NSLog(@"并發(fā)同步over");
打印日志:
<NSThread: 0x7f9be8c05c20>{number = 1, name = main}
<NSThread: 0x7f9be8c05c20>{number = 1, name = main} ~ 0
<NSThread: 0x7f9be8c05c20>{number = 1, name = main} ~ 1
<NSThread: 0x7f9be8c05c20>{number = 1, name = main} ~ 2
<NSThread: 0x7f9be8c05c20>{number = 1, name = main} ~ 3
<NSThread: 0x7f9be8c05c20>{number = 1, name = main} ~ 4
并發(fā)同步over
可以看出這和串行同步的打印日志幾乎一致,由于是串行,所以就只能在線程中一個一個執(zhí)行任務(wù),也就造成了和串行同步一致的效果。
1.4異步并發(fā)
異步并發(fā)可以是多個任務(wù)在不同的線程中同時執(zhí)行。
// 1.創(chuàng)建隊列
dispatch_queue_t queue = dispatch_queue_create("異步并發(fā)", DISPATCH_QUEUE_CONCURRENT);
// 2.將任務(wù)放到隊列中異步執(zhí)行
for (int i = 0; i < 5; ++i) {
dispatch_async(queue, ^{
[self operation:i];
});
}
NSLog(@"異步并發(fā)over again");
打印日志:
異步并發(fā)over again
<NSThread: 0x7ff5f1cca510>{number = 3, name = (null)} ~ 1
<NSThread: 0x7ff5f1d173b0>{number = 2, name = (null)} ~ 0
<NSThread: 0x7ff5f1d0e980>{number = 4, name = (null)} ~ 2
<NSThread: 0x7ff5f1cca510>{number = 3, name = (null)} ~ 3
<NSThread: 0x7ff5f1d173b0>{number = 2, name = (null)} ~ 4
可以看出首先打印的是異步并發(fā)over again,然后在回調(diào)執(zhí)行的任務(wù),而任務(wù)的執(zhí)行也是在不同的線程中同時執(zhí)行的。而迅雷下載也就是應(yīng)用的這種方式。
2. 全局隊列
全局隊列:GCD默認創(chuàng)建的一個并發(fā)隊列,使用的時候只需要獲取
a.同步
for (int i = 0; i < 20; ++i) {
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
[self operation:i];
});
}
這里的 dispatch_get_global_queue(0, 0) 有兩個參數(shù):
參數(shù)1:隊列的優(yōu)先級,優(yōu)先級的有:
DISPATCH_QUEUE_PRIORITY_HIGH:
DISPATCH_QUEUE_PRIORITY_DEFAULT:
DISPATCH_QUEUE_PRIORITY_LOW:
DISPATCH_QUEUE_PRIORITY_BACKGROUND:
參數(shù)2:預(yù)留參數(shù),至今也未用。
打印日志:
<NSThread: 0x7fa1da408ab0>{number = 1, name = main} ~ 0
<NSThread: 0x7fa1da408ab0>{number = 1, name = main} ~ 1
<NSThread: 0x7fa1da408ab0>{number = 1, name = main} ~ 2
<NSThread: 0x7fa1da408ab0>{number = 1, name = main} ~ 3
<NSThread: 0x7fa1da408ab0>{number = 1, name = main} ~ 4
這結(jié)果又和并發(fā)同步執(zhí)行的結(jié)果是一致的。
b.異步
for (int i = 0; i < 20; ++i) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self operation:i];
});
}
執(zhí)行結(jié)果
<NSThread: 0x7feac840ff10>{number = 2, name = (null)} ~ 0
<NSThread: 0x7feac840d2a0>{number = 3, name = (null)} ~ 1
<NSThread: 0x7feac840ff10>{number = 2, name = (null)} ~ 2
<NSThread: 0x7feac840d2a0>{number = 3, name = (null)} ~ 3
<NSThread: 0x7feac840ff10>{number = 2, name = (null)} ~ 4
看出執(zhí)行的結(jié)果和異步并發(fā)的結(jié)果是一致的,是多個任務(wù)在多個線程中執(zhí)行的。
3.主隊列
主隊列是直接和主線程關(guān)聯(lián)的隊列,如果將一個任務(wù)添加到主隊列中,那么這個任務(wù)就只能在主線程中執(zhí)行;主隊列默認已經(jīng)被創(chuàng)建好,使用的時候只需要獲取。
a.同步
for (int i = 0; i < 20; ++i) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self operation:i];
});
}
分析:異步執(zhí)行,需要在不同的線程中執(zhí)行,就需要開不同的線程,而這又是主隊列,所以只能在主線程中執(zhí)行,所以會造成線程沖突或形成死鎖。
而實際的運行結(jié)果也證實了。
b.異步
首先獲取隊列:
dispatch_queue_t queue = dispatch_get_main_queue();
將任務(wù)添加到主隊列中異步執(zhí)行
for (int i = 0; i < 20; ++i) {
dispatch_async(queue, ^{
[self operation:i];
});
}
打印日志:
<NSThread: 0x7fa239402580>{number = 1, name = main} ~ 0
<NSThread: 0x7fa239402580>{number = 1, name = main} ~ 1
<NSThread: 0x7fa239402580>{number = 1, name = main} ~ 2
<NSThread: 0x7fa239402580>{number = 1, name = main} ~ 3
<NSThread: 0x7fa239402580>{number = 1, name = main} ~ 4
這是在主線程中一個一個按順序執(zhí)行。
2.4線程中的通信
設(shè)想下,我們進行網(wǎng)絡(luò)請求的時候,假如有數(shù)據(jù)需要處理的時候,如果使用的是同步串行的這種方式,那這個體驗度肯定是很糟糕的,而我們可以在子線程中進行下載數(shù)據(jù),然后回到主線程中進行展示數(shù)據(jù),而且這只能是異步執(zhí)行的。這中情況就需要線程之間進行通信了。
// 1.下載圖片
// 圖片的下載和展示在全局隊列中執(zhí)行
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"download:%@",[NSThread currentThread]);
// 2.下載圖片任務(wù)
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://attachment.bbs.ptbus.com/data/attachment/forum/201110/15/012639iwopzy3si06y5pbs.jpg"]];
//
UIImage *image = [UIImage imageWithData:imageData];
// 在這兒圖片下載結(jié)束,需要在這里回到主線程
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"show:%@",[NSThread currentThread]);
// 1.展示下載好的圖片
self.imageView.image = image;
});
});
打印的日志
download:<NSThread: 0x7fece24b4860>{number = 2, name = (null)}
show:<NSThread: 0x7fece2407e20>{number = 1, name = main}
可以清楚的看到下載任務(wù)是在子線程中進行的,而展示數(shù)據(jù)是在主線程進行的。
下載的圖片也展示出來了。

2.5隊列組
上面只是執(zhí)行單個任務(wù),如果是很多的任務(wù)的時候,就需要用到隊列組。隊列組的作用就是:如果“希望多個異步執(zhí)行任務(wù),都執(zhí)行完成后再回到主線程”的問題。
假如要求兩個任務(wù)同時執(zhí)行,如果用異步執(zhí)行,下載完成回到主線程的方案是可以解決問題,但其實兩個任務(wù)的過程并不需要按順序執(zhí)行,并發(fā)執(zhí)行它們可以提高執(zhí)行速度。有個注意點就是必須等兩個任務(wù)都執(zhí)行完畢后才能回到主線程顯示的。而Dispatch Group能夠在這種情況下幫我們提升性能。
我們可以使用dispatch_group_async函數(shù)將多個任務(wù)關(guān)聯(lián)到一個Dispatch Group和相應(yīng)的queue中,group會并發(fā)地同時執(zhí)行這些任務(wù)。而且Dispatch Group可以用來阻塞一個線程, 直到group關(guān)聯(lián)的所有的任務(wù)完成執(zhí)行。有時候你必須等待任務(wù)完成的結(jié)果,然后才能繼續(xù)后面的處理。
a.創(chuàng)建一個隊列組
dispatch_group_t group = dispatch_group_create();
dispatch_group_t是一個隊列組類型。
b.將任務(wù)間接放到隊列組中,然后異步執(zhí)行
/**
* 參數(shù)1:隊列組
* 參數(shù)2:隊列
* 參數(shù)2:任務(wù)
*/
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"A:%@",[NSThread currentThread]);
// 任務(wù)A
NSLog(@"down load movie A");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"B:%@",[NSThread currentThread]);
// 任務(wù)B
NSLog(@"down load movie B");
});
c.兩個任務(wù)結(jié)束后回到主線程
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 回到主線程要執(zhí)行的任務(wù)
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"back to main thread");
});
打印結(jié)果
A:<NSThread: 0x7fb93a51abc0>{number = 2, name = (null)}
B:<NSThread: 0x7fb93a5bcdd0>{number = 3, name = (null)}
down load movie A
down load movie B
<NSThread: 0x7fb93a507ef0>{number = 1, name = main}
back to main thread
可以知曉這兩個任務(wù)分別是在不同的線程中執(zhí)行的,最后都是回到主線程展示的。
總結(jié)
1.同步、異步、串行和并發(fā)的四種組合。
- 全局隊列和主隊列。
3.隊列組。