前前言
本文絕大部分內(nèi)容來自
https://www.cnblogs.com/allencelee/p/6023213.html
此文章是學(xué)習(xí)此文章后的新的相關(guān)結(jié)果.
[]是修改/添加的內(nèi)容
代碼和打印結(jié)果都改了一下.
Mac環(huán)境:
macOS High Sierra 10.13.4
Xcode9.2
手機(jī):
iOS11.1.2 iPhone6s
前言
對(duì)初學(xué)者來說,GCD似乎是一道邁不過去的坎,很多人在同步、異步、串行、并行和死鎖這幾個(gè)名詞的漩渦中漸漸放棄治療。本文將使用圖文表并茂的方式給大家形象地解釋其中的原理和規(guī)律。
線程、任務(wù)和隊(duì)列的概念

異步、同步 & 并行、串行的特點(diǎn)

[添加]
同步執(zhí)行一定是在當(dāng)前線程,無論是在并行隊(duì)列還是在串行隊(duì)列!
異步執(zhí)行是具備開啟新線程,但是不一定100%開啟新線程,如果有線程空余就直接拿來用!
一條重要的準(zhǔn)則
一般來說,我們使用GCD的最大目的是在新的線程中同時(shí)執(zhí)行多個(gè)任務(wù),這意味著我們需要兩項(xiàng)條件:
1.能開啟新的線程
2.任務(wù)可以同時(shí)執(zhí)行
結(jié)合以上兩個(gè)條件,也就等價(jià)“開啟新線程的能力 + 任務(wù)同步執(zhí)行的權(quán)利”,只有在滿足能力與權(quán)利這兩個(gè)條件的前提下,我們才可以在同時(shí)執(zhí)行多個(gè)任務(wù)。
所有組合的特點(diǎn)([添加]在主線程和主隊(duì)列執(zhí)行的情況下)

(一)異步執(zhí)行 + 并行隊(duì)列
實(shí)現(xiàn)代碼:
// 異步執(zhí)行 + 并行隊(duì)列
- (void)asyncConcurrent {
printf("\n================%s================\n", [NSStringFromSelector(_cmd) UTF8String]);
printf("main: %s\n", [[[NSThread mainThread] description] UTF8String]);
//創(chuàng)建一個(gè)并行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("標(biāo)識(shí)符", DISPATCH_QUEUE_CONCURRENT);
printf("---start---\n");
//使用異步函數(shù)封裝三個(gè)任務(wù)
dispatch_async(queue, ^{
printf("任務(wù)1---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
dispatch_async(queue, ^{
printf("任務(wù)2---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
dispatch_async(queue, ^{
printf("任務(wù)3---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
// 這一段代碼可以證明end輸出的順序不確定,主要是看哪個(gè)先執(zhí)行完畢
// sleep(1);
printf("---end---\n");
}
打印結(jié)果:
================asyncConcurrent================
main: <NSThread: 0x1c407ad00>{number = 1, name = main}
---start---
---end---
任務(wù)1---<NSThread: 0x1c047fb00>{number = 4, name = (null)}
任務(wù)3---<NSThread: 0x1c047fb00>{number = 4, name = (null)}
任務(wù)2---<NSThread: 0x1c047fb80>{number = 3, name = (null)}
解釋
異步執(zhí)行意味著
可以開啟新的線程
任務(wù)可以先繞過不執(zhí)行,回頭再來執(zhí)行
并行隊(duì)列意味著
任務(wù)之間不需要排隊(duì),且具有同時(shí)被執(zhí)行的“權(quán)利”
兩者組合后的結(jié)果
開了三個(gè)新線程
函數(shù)在執(zhí)行時(shí),先打印了start和end,再回頭執(zhí)行這三個(gè)任務(wù)
[修改]這三個(gè)任務(wù)是同時(shí)執(zhí)行的,沒有先后,所以打印結(jié)果是隨機(jī)的,
關(guān)于開線程有可能是開啟1個(gè),2個(gè),3個(gè),這個(gè)是隨機(jī)的,
這個(gè)可以重復(fù)的運(yùn)行幾次,每次的結(jié)果都不一定一致.
end與任務(wù)1,2,3 輸出的順序不一定
步驟圖

(二)異步執(zhí)行 + 串行隊(duì)列
實(shí)現(xiàn)代碼:
// 異步執(zhí)行 + 串行隊(duì)列
- (void)asyncSerial {
printf("\n================%s================\n", [NSStringFromSelector(_cmd) UTF8String]);
printf("main: %s\n", [[[NSThread mainThread] description] UTF8String]);
//創(chuàng)建一個(gè)串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("標(biāo)識(shí)符", DISPATCH_QUEUE_SERIAL);
printf("---start---\n");
//使用異步函數(shù)封裝三個(gè)任務(wù)
dispatch_async(queue, ^{
printf("任務(wù)1---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
dispatch_async(queue, ^{
printf("任務(wù)2---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
dispatch_async(queue, ^{
printf("任務(wù)3---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
printf("---end---\n");
}
打印結(jié)果:
================asyncSerial================
main: <NSThread: 0x1c4073740>{number = 1, name = main}
---start---
---end---
任務(wù)1---<NSThread: 0x1c44720c0>{number = 3, name = (null)}
任務(wù)2---<NSThread: 0x1c44720c0>{number = 3, name = (null)}
任務(wù)3---<NSThread: 0x1c44720c0>{number = 3, name = (null)}
解釋
異步執(zhí)行意味著
可以開啟新的線程
任務(wù)可以先繞過不執(zhí)行,回頭再來執(zhí)行
串行隊(duì)列意味著
任務(wù)必須按添加進(jìn)隊(duì)列的順序挨個(gè)執(zhí)行
兩者組合后的結(jié)果
開了一個(gè)新的子線程
函數(shù)在執(zhí)行時(shí),先打印了start和end,再回頭執(zhí)行這三個(gè)任務(wù)
這三個(gè)任務(wù)是按順序執(zhí)行的,所以打印結(jié)果是“任務(wù)1-->任務(wù)2-->任務(wù)3”
步驟圖

(三)同步執(zhí)行 + 并行隊(duì)列
實(shí)現(xiàn)代碼:
// 同步執(zhí)行 + 并行隊(duì)列
- (void)syncConcurrent {
printf("\n================%s================\n", [NSStringFromSelector(_cmd) UTF8String]);
printf("main: %s\n", [[[NSThread mainThread] description] UTF8String]);
//創(chuàng)建一個(gè)并行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("標(biāo)識(shí)符", DISPATCH_QUEUE_CONCURRENT);
printf("---start---\n");
//使用同步函數(shù)封裝三個(gè)任務(wù)
dispatch_sync(queue, ^{
printf("任務(wù)1---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
dispatch_sync(queue, ^{
printf("任務(wù)2---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
dispatch_sync(queue, ^{
printf("任務(wù)3---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
printf("---end---\n");
}
打印結(jié)果:
================syncConcurrent================
main: <NSThread: 0x1c407c800>{number = 1, name = main}
---start---
任務(wù)1---<NSThread: 0x1c407c800>{number = 1, name = main}
任務(wù)2---<NSThread: 0x1c407c800>{number = 1, name = main}
任務(wù)3---<NSThread: 0x1c407c800>{number = 1, name = main}
---end---
解釋
同步執(zhí)行執(zhí)行意味著
不能開啟新的線程
任務(wù)創(chuàng)建后必須執(zhí)行完才能往下走
并行隊(duì)列意味著
任務(wù)必須按添加進(jìn)隊(duì)列的順序挨個(gè)執(zhí)行
兩者組合后的結(jié)果
所有任務(wù)都只能在主線程中執(zhí)行
函數(shù)在執(zhí)行時(shí),必須按照代碼的書寫順序一行一行地執(zhí)行完才能繼續(xù)
注意事項(xiàng)
在這里即便是并行隊(duì)列,任務(wù)可以同時(shí)執(zhí)行,
但是由于只存在一個(gè)主線程,所以沒法把任務(wù)分發(fā)到不同的線程去同步處理,
其結(jié)果就是只能在主線程里按順序挨個(gè)挨個(gè)執(zhí)行了
步驟圖

(四)同步執(zhí)行+ 串行隊(duì)列
實(shí)現(xiàn)代碼:
// 同步執(zhí)行 + 串行隊(duì)列
- (void)syncSerial{
printf("\n================%s================\n", [NSStringFromSelector(_cmd) UTF8String]);
printf("main: %s\n", [[[NSThread mainThread] description] UTF8String]);
//創(chuàng)建一個(gè)串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("標(biāo)識(shí)符", DISPATCH_QUEUE_SERIAL);
printf("---start---\n");
//使用異步函數(shù)封裝三個(gè)任務(wù)
dispatch_sync(queue, ^{
printf("任務(wù)1---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
dispatch_sync(queue, ^{
printf("任務(wù)2---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
dispatch_sync(queue, ^{
printf("任務(wù)3---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
printf("---end---\n");
}
打印結(jié)果:
================syncSerial================
main: <NSThread: 0x1c006d1c0>{number = 1, name = main}
---start---
任務(wù)1---<NSThread: 0x1c006d1c0>{number = 1, name = main}
任務(wù)2---<NSThread: 0x1c006d1c0>{number = 1, name = main}
任務(wù)3---<NSThread: 0x1c006d1c0>{number = 1, name = main}
---end---
解釋
這里的執(zhí)行原理和步驟圖跟“同步執(zhí)行+并發(fā)隊(duì)列”是一樣的,
只要是同步執(zhí)行就沒法開啟新的線程,所以多個(gè)任務(wù)之間也一樣只能按順序來執(zhí)行,
(五)異步執(zhí)行+主隊(duì)列
實(shí)現(xiàn)代碼:
// 異步執(zhí)行 + 主隊(duì)列
- (void)asyncMain {
printf("\n================%s================\n", [NSStringFromSelector(_cmd) UTF8String]);
printf("main: %s\n", [[[NSThread mainThread] description] UTF8String]);
//獲取主隊(duì)列
dispatch_queue_t queue = dispatch_get_main_queue();
printf("---start---\n");
//使用異步函數(shù)封裝三個(gè)任務(wù)
dispatch_async(queue, ^{
printf("任務(wù)1---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
dispatch_async(queue, ^{
printf("任務(wù)2---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
dispatch_async(queue, ^{
printf("任務(wù)3---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
printf("---end---\n");
}
打印結(jié)果:
================asyncMain================
main: <NSThread: 0x1c4070d80>{number = 1, name = main}
---start---
---end---
任務(wù)1---<NSThread: 0x1c4070d80>{number = 1, name = main}
任務(wù)2---<NSThread: 0x1c4070d80>{number = 1, name = main}
任務(wù)3---<NSThread: 0x1c4070d80>{number = 1, name = main}
解釋
異步執(zhí)行意味著
可以開啟新的線程
任務(wù)可以先繞過不執(zhí)行,回頭再來執(zhí)行
主隊(duì)列跟串行隊(duì)列的區(qū)別
隊(duì)列中的任務(wù)一樣要按順序執(zhí)行
主隊(duì)列中的任務(wù)必須在主線程中執(zhí)行,不允許在子線程中執(zhí)行
以上條件組合后得出結(jié)果:
所有任務(wù)都可以先跳過,之后再來“按順序”執(zhí)行
步驟圖

(六)同步執(zhí)行+主隊(duì)列(死鎖)
實(shí)現(xiàn)代碼:
// 同步執(zhí)行 + 主隊(duì)列
- (void)syncMain {
printf("\n================%s================\n", [NSStringFromSelector(_cmd) UTF8String]);
printf("main: %s\n", [[[NSThread mainThread] description] UTF8String]);
//獲取主隊(duì)列
dispatch_queue_t queue = dispatch_get_main_queue();
printf("---start---\n");
//使用同步函數(shù)封裝三個(gè)任務(wù)
dispatch_sync(queue, ^{
printf("任務(wù)1---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
dispatch_sync(queue, ^{
printf("任務(wù)2---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
dispatch_sync(queue, ^{
printf("任務(wù)3---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
printf("---end---\n");
}
執(zhí)行結(jié)果:
================syncMain================
main: <NSThread: 0x1c407f2c0>{number = 1, name = main}
---start---
[添加]程序會(huì)直接崩潰(看來蘋果已經(jīng)能直接發(fā)現(xiàn)這個(gè)死鎖)
解釋
主隊(duì)列中的任務(wù)必須按順序挨個(gè)執(zhí)行
任務(wù)1要等主線程有空的時(shí)候(即主隊(duì)列中的所有任務(wù)執(zhí)行完)才能執(zhí)行
主線程要執(zhí)行完“打印end”的任務(wù)后才有空
"任務(wù)1"和"打印end"兩個(gè)任務(wù)互相等待,造成死鎖
步驟圖

[以下都是添加的]
以上代碼都是在主線程執(zhí)行的,關(guān)于第六段代碼,可以用如下的代碼測(cè)試.syn
// 同步執(zhí)行 + 一個(gè)串行隊(duì)列, 類似于同步執(zhí)行 + 主隊(duì)列
- (void)syncSerialInSerial {
printf("\n================%s================\n", [NSStringFromSelector(_cmd) UTF8String]);
printf("main: %s\n", [[[NSThread mainThread] description] UTF8String]);
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
printf("---main start---\n");
// 這里使用 dispatch_sync 也會(huì)導(dǎo)致死鎖
dispatch_async(queue, ^{
printf("---queue start---\n");
printf("queue start(用dispatch_sync是主線程,用dispatch_async會(huì)開啟一個(gè)線程)---%s\n", [[[NSThread currentThread] description] UTF8String]);
//使用同步函數(shù)封裝三個(gè)任務(wù)
dispatch_sync(queue, ^{
printf("任務(wù)1---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
dispatch_sync(queue, ^{
printf("任務(wù)2---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
dispatch_sync(queue, ^{
printf("任務(wù)3---%s\n", [[[NSThread currentThread] description] UTF8String]);
});
printf("---queue end---\n");
});
printf("---當(dāng)最外面是dispatch_async輸出,如果是dispatch_sync不輸出---\n");
printf("---main end---\n");
}
打印結(jié)果:
================syncSerialInSerial================
main: <NSThread: 0x1c00779c0>{number = 1, name = main}
---main start---
---queue start---
queue start---<NSThread: 0x1c00779c0>{number = 1, name = main}
上面的程序也會(huì)崩潰.
關(guān)于gcd的更多死鎖可以看
http://www.knowsky.com/884482.html
GCD實(shí)現(xiàn)多讀單寫:
.h文件
@interface RXMRSWPerson : NSObject
// 當(dāng)同時(shí)設(shè)置set和get的時(shí)候,不會(huì)自動(dòng)生成_name變量
@property (nonatomic, copy) NSString *name;
@end
.m文件
@interface RXMRSWPerson() {
@private
NSString *_name;
}
@property (nonatomic, strong) dispatch_queue_t queue;
@end
@implementation RXMRSWPerson
- (id)init {
if (self = [super init]) {
_name = @"";
}
return self;
}
- (dispatch_queue_t)queue {
if (_queue == nil) {
_queue = dispatch_queue_create("com.rxmrswperson.com", DISPATCH_QUEUE_CONCURRENT);
}
return _queue;
}
- (void)setName:(NSString *)name {
dispatch_barrier_async(self.queue, ^{
_name = name;
});
}
- (NSString *)name {
__block NSString *tmpName;
dispatch_sync(self.queue, ^{
tmpName = _name;
});
return tmpName;
}
測(cè)試?
- (void)testPerson {
RXMRSWPerson *person = [RXMRSWPerson new];
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_queue_t serialQueue = dispatch_queue_create("com.serail.queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
person.name = @"init";
dispatch_async(mainQueue, ^{
NSLog(@"person name in mainQueue:%@", person.name);
});
dispatch_async(globalQueue, ^{
NSLog(@"person name in globalQueue:%@", person.name);
person.name = @"globalQueue";
});
dispatch_async(serialQueue, ^{
person.name = @"serialQueue";
NSLog(@"person name in serialQueue:%@", person.name);
});
dispatch_async(concurrentQueue, ^{
person.name = @"concurrentQueue";
});
NSLog(@"person name:%@", person.name);
}
判斷當(dāng)前執(zhí)行隊(duì)列是否是某個(gè)隊(duì)列
- (BOOL)isCurQueueEqualQueue:(dispatch_queue_t)queue {
NSString *specificKey = @"com.specific.specificKey";
NSString *specificValue = [NSString stringWithFormat:@"com.specific.sepcificValue.%@", [NSDate new]];
dispatch_queue_set_specific(queue, &specificKey, (__bridge void *)specificValue, NULL);
NSString *retrievedValue = (__bridge NSString *)dispatch_get_specific(&specificKey);
return [retrievedValue isEqualToString:specificValue];
}
dispatch_queue_set_specific 、dispatch_get_specific
這兩個(gè)API類似于objc_setAssociatedObject跟objc_getAssociatedObject,
Target Queue的例子:
https://blog.csdn.net/growinggiant/article/details/41077221
/**
下邊來看更有意思的,一般都是把一個(gè)任務(wù)放到一個(gè)串行的queue中,如果這個(gè)任務(wù)被拆分了,被放置到多個(gè)串行的queue中,但實(shí)際還是需要這個(gè)任務(wù)同步執(zhí)行,那么就會(huì)有問題,因?yàn)槎鄠€(gè)串行queue之間是并行的。
那該如何是好呢?
這是就可以使用dispatch_set_target_queue了。
如果將多個(gè)串行的queue使用dispatch_set_target_queue指定到了同一目標(biāo),那么著多個(gè)串行queue在目標(biāo)queue上就是同步執(zhí)行的,不再是并行執(zhí)行。
*/
- (void)testTargetQueue {
dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
// dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_set_target_queue(queue3, targetQueue);
dispatch_async(queue1, ^{
NSLog(@"1 in");
[NSThread sleepForTimeInterval:3.f];
NSLog(@"1 out");
});
dispatch_async(queue1, ^{
NSLog(@"4 in");
[NSThread sleepForTimeInterval:3.f];
NSLog(@"4 out");
});
dispatch_async(queue2, ^{
NSLog(@"2 in");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"2 out");
});
dispatch_async(queue3, ^{
NSLog(@"3 in");
[NSThread sleepForTimeInterval:1.f];
NSLog(@"3 out");
});
}