iOS-GCD使用詳解

前前言
本文絕大部分內(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ì)列的概念


image

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


image

[添加]
同步執(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í)行的情況下)


在主線程和主隊(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 輸出的順序不一定

步驟圖


image

(二)異步執(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”

步驟圖


image

(三)同步執(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í)行了

步驟圖


image

(四)同步執(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í)行

步驟圖


image

(六)同步執(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ù)互相等待,造成死鎖

步驟圖


image

[以下都是添加的]
以上代碼都是在主線程執(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");
    });
    
    
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容