GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)

本文是GCD多線程編程基礎(chǔ)內(nèi)容的小結(jié),通過本文,你可以了解到:

  • 多線程的幾個(gè)基本概念:進(jìn)程與線程、串行與并發(fā)
  • GCD中的2個(gè)核心內(nèi)容:隊(duì)列、任務(wù)
  • GCD的基本使用步驟
  • GCD中使用同步異步方式添加任務(wù)到串行并發(fā)隊(duì)列后執(zhí)行的實(shí)際效果
  • GCD中產(chǎn)生死鎖的原因以及實(shí)際開發(fā)中如何避免死鎖crash

GCD

Apple為了讓開發(fā)者更加容易的使用設(shè)備上的多核CPU,蘋果在 OS X 10.6 和 iOS 4 中引入了 Grand Central Dispatch(GCD),它是 Apple 開發(fā)的一個(gè)多核編程的較新的解決方法,它主要用于優(yōu)化應(yīng)用程序以支持多核處理器,它是一個(gè)在線程池模式的基礎(chǔ)上執(zhí)行的并發(fā)任務(wù),是我們平常開發(fā)中最常見的一種多線程編程方式

測(cè)試代碼在這

多線程基本概念

進(jìn)程與線程

對(duì)于操作系統(tǒng)來說,一個(gè)任務(wù)就是一個(gè)進(jìn)程(Process),比如打開一個(gè)瀏覽器就是啟動(dòng)一個(gè)瀏覽器進(jìn)程,打開一個(gè)記事本就啟動(dòng)了一個(gè)記事本進(jìn)程,打開兩個(gè)記事本就啟動(dòng)了兩個(gè)記事本進(jìn)程,打開一個(gè)Word就啟動(dòng)了一個(gè)Word進(jìn)程。

有些進(jìn)程還不止同時(shí)干一件事,比如Word,它可以同時(shí)進(jìn)行打字、拼寫檢查、打印等事情。在一個(gè)進(jìn)程內(nèi)部,要同時(shí)干多件事,就需要同時(shí)運(yùn)行多個(gè)“子任務(wù)”,我們把進(jìn)程內(nèi)的這些“子任務(wù)”稱為線程(Thread)。

由于每個(gè)進(jìn)程至少要干一件事,所以,一個(gè)進(jìn)程至少有一個(gè)線程。當(dāng)然,像Word這種復(fù)雜的進(jìn)程可以有多個(gè)線程,多個(gè)線程可以同時(shí)執(zhí)行,多線程的執(zhí)行方式和多進(jìn)程是一樣的,也是由操作系統(tǒng)在多個(gè)線程之間快速切換,讓每個(gè)線程都短暫地交替運(yùn)行,看起來就像同時(shí)執(zhí)行一樣。當(dāng)然,真正地同時(shí)執(zhí)行多線程需要多核CPU才可能實(shí)現(xiàn)。

串行和并發(fā)

并發(fā)就是多個(gè)任務(wù)在執(zhí)行的過程中,時(shí)間互相重疊,一個(gè)任務(wù)執(zhí)行沒結(jié)束,另一個(gè)已經(jīng)開始。

串行就是任務(wù)一個(gè)一個(gè)的執(zhí)行,時(shí)間上不相互重疊,一個(gè)任務(wù)執(zhí)行結(jié)束,下一個(gè)任務(wù)才能開始執(zhí)行。

GCG隊(duì)列

隊(duì)列是一種特殊的線性表,特殊之處在于它只允許在表的后端進(jìn)入插入操作,在表的前端進(jìn)行刪除操作,即遵循FIFO原則。

GCD中的隊(duì)列(Dispatch Queue)就是指用來執(zhí)行任務(wù)的等待隊(duì)列,當(dāng)我們添加任務(wù)到隊(duì)列之后,開發(fā)者不用再直接跟線程打交道了,只需要向隊(duì)列中添加代碼塊即可,GCD 在后端管理著一個(gè)線程池。GCD 不僅決定著你的代碼塊將在哪個(gè)線程被執(zhí)行,它還根據(jù)可用的系統(tǒng)資源對(duì)這些線程進(jìn)行管理。這樣可以將開發(fā)者從線程管理的工作中解放出來,通過集中的管理線程,來緩解大量線程被創(chuàng)建的問題。

GCD中的隊(duì)列可以分為以下2種:

  • 串行隊(duì)列 ( Serial Dispatch Queue )

    串行隊(duì)列(也稱為私有調(diào)度隊(duì)列)按照將他們添加到隊(duì)列順序一次執(zhí)行一個(gè)任務(wù)。當(dāng)前正在執(zhí)行的任務(wù)在由隊(duì)列管理的不同線程(可能因任務(wù)而異)上運(yùn)行。串行隊(duì)列通常用于同步對(duì)特定資源的訪問。

  • 并發(fā)隊(duì)列 ( Concurrent Dispatch Queue )

    并發(fā)隊(duì)列(也稱為一種全局調(diào)度隊(duì)列)同時(shí)執(zhí)行一個(gè)或多個(gè)任務(wù),但任務(wù)仍按其添加到隊(duì)列的順序啟動(dòng)。當(dāng)前正在執(zhí)行的任務(wù)在由調(diào)度隊(duì)列管理的不同線程上運(yùn)行。在任何給定點(diǎn)執(zhí)行的任務(wù)的確切數(shù)量是可變的,取決于系統(tǒng)條件。

在我們平時(shí)的開發(fā)中,還有2種我們最常見的,也是使用頻率最高的隊(duì)列:

  • 主隊(duì)列 ( Main Dispatch Queue )

    主隊(duì)列是一個(gè)全局可用的串行隊(duì)列,它在應(yīng)用程序的主線程上執(zhí)行任務(wù)。此隊(duì)列與應(yīng)用程序的Runloop一起工作,將有序任務(wù)的執(zhí)行與附加到Runloop的其他事件源的執(zhí)行交錯(cuò)。因?yàn)樗趹?yīng)用程序的主線程上運(yùn)行,所以主隊(duì)列通常用作應(yīng)用程序的關(guān)鍵同步點(diǎn)。

    主隊(duì)列下的任務(wù)不管是異步任務(wù)還是同步任務(wù)都不會(huì)開辟線程,任務(wù)只會(huì)在主線程順序執(zhí)行

  • 全局并發(fā)隊(duì)列 ( Global Dispatch Queue )

    全局并發(fā)隊(duì)列本質(zhì)上是一個(gè)并發(fā)隊(duì)列,有系統(tǒng)提供,方便編程,可以不用創(chuàng)建就可以直接使用

GCD任務(wù)

任務(wù)就是你要在線程中執(zhí)行的代碼,在GCD中是用Block來定義任務(wù)的,是用起來非常靈活便捷。

GCD中執(zhí)行任務(wù)的方式有兩種:同步執(zhí)行(sync)與異步執(zhí)行(async)

  • 同步執(zhí)行

    同步執(zhí)行就是指使用 dispatch_sync方法將任務(wù)同步的添加到隊(duì)列里,在添加的任務(wù)執(zhí)行結(jié)束之前,當(dāng)前線程會(huì)被阻塞,然后會(huì)一直等待,直到任務(wù)完成。

    dispatch_sync添加的任務(wù)只能在當(dāng)前線程執(zhí)行,不具備開啟新線程的能力

  • 異步執(zhí)行

    異步執(zhí)行就是指使用dispatch_async方法將任務(wù)異步的添加到隊(duì)列里,它不需要等待任務(wù)執(zhí)行結(jié)束,不需要做任何等待就能繼續(xù)執(zhí)行任務(wù)

    dispatch_async添加的任務(wù)可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力,但并不一定會(huì)開啟新線程

GCD的使用步驟

這個(gè)就跟趙本山跟宋丹丹的小品《鐘點(diǎn)工》里提出的把大象裝進(jìn)冰箱的經(jīng)典問題一樣,都是分三步:

把大象裝進(jìn)冰箱

  1. 把冰箱門打開
  2. 把大象裝進(jìn)去
  3. 把冰箱門關(guān)上

GCD使用步驟

  1. 創(chuàng)建或獲取一個(gè)隊(duì)列
  2. 定制需要執(zhí)行的任務(wù)
  3. 將任務(wù)追加到隊(duì)列

創(chuàng)建或獲取一個(gè)隊(duì)列

  • 使用dispatch_get_main_queue() 獲取主隊(duì)列。

  • 使用dispatch_get_global_queue獲取全局并發(fā)隊(duì)列,這個(gè)函數(shù)有2個(gè)參數(shù),第一個(gè)參數(shù)是全局隊(duì)列的優(yōu)先級(jí),一般情況下,使用的都是DISPATCH_QUEUE_PRIORITY_DEFAULT優(yōu)先級(jí),第二個(gè)參數(shù)是一個(gè)保留字段,我們需要給它一個(gè)0,否則這個(gè)函數(shù)會(huì)返回一個(gè)NULL,導(dǎo)致我們獲取不到正常的全局隊(duì)列。

    Reserved for future use. Passing any value other than zero may result in a NULL return value.
    
  • 使用dispatch_queue_create函數(shù),創(chuàng)建自定義的串行或并行隊(duì)列,這個(gè)函數(shù)的定義如下:

     * @param label
     * A string label to attach to the queue.
     * This parameter is optional and may be NULL.
     *
     * @param attr
     * A predefined attribute such as DISPATCH_QUEUE_SERIAL,
     * DISPATCH_QUEUE_CONCURRENT, or the result of a call to
     * a dispatch_queue_attr_make_with_* function.
     *
     * @result
     * The newly created dispatch queue.
     */
    API_AVAILABLE(macos(10.6), ios(4.0))
    DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT
    DISPATCH_NOTHROW
    dispatch_queue_t
    dispatch_queue_create(const char *_Nullable label,
          dispatch_queue_attr_t _Nullable attr);
    

    dispatch_queue_create函數(shù)最后返回了一個(gè)隊(duì)列dispatch_queue_t,這個(gè)函數(shù)有2個(gè)參數(shù),第一個(gè)參數(shù)其實(shí)可以看成是是我們給這個(gè)隊(duì)列取的名字,以便后續(xù)debug,蘋果官方是推薦開發(fā)者使用逆序全程域名。

    第二個(gè)參數(shù),是用于確定這個(gè)隊(duì)列是串行隊(duì)列還是并發(fā)隊(duì)列,使用DISPATCH_QUEUE_CONCURRENT表示創(chuàng)建的隊(duì)列是并發(fā)隊(duì)列,使用DISPATCH_QUEUE_SERIAL或者NULL表示創(chuàng)建的隊(duì)列是串行隊(duì)列,它們兩個(gè)其實(shí)是等價(jià)的,見下面的注釋:

    /*!
     * @const DISPATCH_QUEUE_SERIAL
     *
     * @discussion
     * An attribute that can be used to create a dispatch queue that invokes blocks
     * serially in FIFO order.
     *
     * See dispatch_queue_serial_t.
     */
    #define DISPATCH_QUEUE_SERIAL NULL
    

    不過個(gè)人不推薦使用NULL的方式來表示創(chuàng)建的是串行隊(duì)列,這種方式在多人開發(fā)時(shí),閱讀性是比較差的。

    以下是獲取或創(chuàng)建的4種方式:

        //獲取自定義串行隊(duì)列
        self.serialQueue = dispatch_queue_create("com.zed.customSerialQueue", DISPATCH_QUEUE_SERIAL);
        //獲取自定義并發(fā)隊(duì)列
        self.concurrentQueue = dispatch_queue_create("com.zed.customConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
        //獲取主隊(duì)列
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        //獲取全局并發(fā)隊(duì)列
        dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    

定制需要執(zhí)行的任務(wù)

GCD種的任務(wù)其實(shí)就是一個(gè)Block,就是我們俗稱的代碼塊,在這個(gè)代碼塊里面,把我們需要做的事情就是,將我們的任務(wù)代碼加入到這個(gè)block中

void (^block)(void) = ^{
        NSLog(@"執(zhí)行任務(wù)");
        for (int i = 0; i<100; i++) {
            NSLog(@"%d",i);
        }
        NSLog(@"Thread:%@",[NSThread currentThread]);
    };

將任務(wù)追加到隊(duì)列

GCD提供了2個(gè)方法用于將任務(wù)追加到隊(duì)列:

  1. dispatch_sync 使用同步執(zhí)行的方式追加到隊(duì)列
  2. dispatch_async 使用異步的方式追加到隊(duì)列
//三、將任務(wù)增加到隊(duì)列中
dispatch_async(globalQueue, block);

GCD的基本使用

前面我們已經(jīng)介紹了兩種基本隊(duì)列(串行隊(duì)列與并發(fā)隊(duì)列),兩種特殊隊(duì)列(主隊(duì)列與全局并發(fā)隊(duì)列),兩種任務(wù)執(zhí)行方式(同步執(zhí)行與異步執(zhí)行),所以,我們就有了8中不同的組合方式,不過由于全局并發(fā)隊(duì)列跟普通并發(fā)隊(duì)列的性質(zhì)是差不多的,所以,我們就有6中不同的組合,接下來,我們從3個(gè)角度來觀察這6種組合方式的效果:

三個(gè)角度

  1. 是否開啟線程
  2. 任務(wù)是按序執(zhí)行還是交替(同時(shí))執(zhí)行
  3. 是否阻塞當(dāng)前線程

六種組合方式

  1. 同步執(zhí)行+并發(fā)隊(duì)列
  2. 異步執(zhí)行+并發(fā)隊(duì)列
  3. 同步執(zhí)行+串行隊(duì)列
  4. 異步執(zhí)行+串行隊(duì)列
  5. 同步執(zhí)行+主隊(duì)列
  6. 異步執(zhí)行+主隊(duì)列

同步執(zhí)行+并發(fā)隊(duì)列

#pragma mark - 同步執(zhí)行+并發(fā)隊(duì)列
/*
 * 特點(diǎn):
 * 1.在當(dāng)前線程中執(zhí)行任務(wù),不會(huì)開啟新線程
 * 2.按序執(zhí)行任務(wù),執(zhí)行行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)
 * 3.會(huì)阻塞當(dāng)前線程
 */
- (IBAction)executeSyncConcurrencyTask:(UIButton *)sender {
    
    NSLog(@"CurrentThread---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"SyncConcurrencyTask---begin");
    
    dispatch_sync(self.concurrentQueue, ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_sync(self.concurrentQueue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_sync(self.concurrentQueue, ^{
        // 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    NSLog(@"SyncConcurrencyTask---end");
    NSLog(@"*********************************************************");
}

執(zhí)行結(jié)果如下:

2019-04-22 13:42:07.265811+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[8668:1868026] CurrentThread---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:07.265945+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[8668:1868026] SyncConcurrencyTask---begin
2019-04-22 13:42:09.266681+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[8668:1868026] 1---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:11.268049+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[8668:1868026] 1---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:12.299360+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[8668:1868133] XPC connection interrupted
2019-04-22 13:42:13.269544+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[8668:1868026] 2---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:15.270540+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[8668:1868026] 2---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:17.271936+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[8668:1868026] 3---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:19.273478+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[8668:1868026] 3---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:19.273713+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[8668:1868026] SyncConcurrencyTask---end
2019-04-22 13:42:19.273857+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[8668:1868026] *********************************************************

通過我們的代碼測(cè)驗(yàn),可以看出:

  1. 所有的任務(wù)都是在主線程(當(dāng)前線程)中執(zhí)行的,并沒有開啟新的線程,這也說明了同步執(zhí)行的一個(gè)特性:同步執(zhí)行任務(wù)不具備開啟新線程的能力。

  2. 任務(wù)1、任務(wù)2、任務(wù)3是按順序執(zhí)行的,并沒有出現(xiàn)并發(fā)執(zhí)行的情況,這是因?yàn)殡m然并發(fā)隊(duì)列具備同時(shí)執(zhí)行多個(gè)任務(wù)的能力,但是由于是同步執(zhí)行不具備開啟新線程的能力,所以,即使任務(wù)被追加到了并發(fā)隊(duì)列,它也沒有辦法去開啟新的線程,只能在當(dāng)前線程中執(zhí)行任務(wù)。

  3. 從我們的log中可以看出,我們所有的任務(wù)都是在 beginend之間的,所以說,它會(huì)阻塞當(dāng)前線程,等待隊(duì)列中的任務(wù)執(zhí)行結(jié)束,才會(huì)繼續(xù)執(zhí)行下面的代碼。

異步執(zhí)行+并發(fā)隊(duì)列

#pragma mark - 異步執(zhí)行+并發(fā)隊(duì)列
/*
 * 特點(diǎn):
 * 1.開啟多個(gè)新線程執(zhí)行任務(wù)
 * 2.任務(wù)交替(同時(shí))執(zhí)行
 * 3.不會(huì)阻塞當(dāng)前線程
 */
- (IBAction)executeAsyncConcurrencyTask:(UIButton *)sender {
    
    NSLog(@"CurrentThread begin---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"AsyncConcurrencyTask---begin");
    
    dispatch_async(self.concurrentQueue, ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(self.concurrentQueue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(self.concurrentQueue, ^{
        // 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    NSLog(@"CurrentThread end---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"AsyncConcurrencyTask---end");
    NSLog(@"*********************************************************");
}

執(zhí)行結(jié)果如下:

2019-04-22 14:32:14.941222+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9417:2009244] CurrentThread begin---<NSThread: 0x6000000cea40>{number = 1, name = main}
2019-04-22 14:32:14.941405+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9417:2009244] AsyncConcurrencyTask---begin
2019-04-22 14:32:14.941608+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9417:2009244] CurrentThread end---<NSThread: 0x6000000cea40>{number = 1, name = main}
2019-04-22 14:32:14.941757+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9417:2009244] AsyncConcurrencyTask---end
2019-04-22 14:32:14.941894+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9417:2009244] *********************************************************
2019-04-22 14:32:16.945860+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9417:2009294] 1---<NSThread: 0x6000000a2380>{number = 4, name = (null)}
2019-04-22 14:32:16.945909+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9417:2011461] 3---<NSThread: 0x6000000a2340>{number = 6, name = (null)}
2019-04-22 14:32:16.945909+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9417:2011460] 2---<NSThread: 0x6000000aec00>{number = 5, name = (null)}
2019-04-22 14:32:18.951120+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9417:2011460] 2---<NSThread: 0x6000000aec00>{number = 5, name = (null)}
2019-04-22 14:32:18.951121+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9417:2011461] 3---<NSThread: 0x6000000a2340>{number = 6, name = (null)}
2019-04-22 14:32:18.951120+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9417:2009294] 1---<NSThread: 0x6000000a2380>{number = 4, name = (null)}

通過我們的代碼測(cè)驗(yàn),可以看出:

  1. 除了在主線程執(zhí)行的2個(gè)log任務(wù)之外,系統(tǒng)又開啟了3個(gè)線程用于執(zhí)行追加的三個(gè)任務(wù),說明異步執(zhí)行具備開啟新線程的能力,并且并發(fā)隊(duì)列可以開啟多個(gè)線程,交替執(zhí)行多個(gè)任務(wù)。
  2. 從我們的log中可以看到,begin的log之后,馬上就是end的log,因此可以看出,它并不會(huì)阻塞當(dāng)前線程,并不需要等待追加的任務(wù)執(zhí)行完成。

同步執(zhí)行+串行隊(duì)列

#pragma mark - 同步執(zhí)行+串行隊(duì)列
/*
 * 特點(diǎn):
 * 1.在當(dāng)前線程中執(zhí)行任務(wù),不會(huì)開啟新線程
 * 2.按序執(zhí)行任務(wù),執(zhí)行行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)
 * 3.會(huì)阻塞當(dāng)前線程
 */
- (IBAction)executeSyncSerialTask:(UIButton *)sender {
    
    NSLog(@"CurrentThread begin---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"SyncSerialTask---begin");
    
    dispatch_sync(self.serialQueue, ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_sync(self.serialQueue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_sync(self.serialQueue, ^{
        // 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    NSLog(@"CurrentThread end---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"SyncSerialTask---end");
    NSLog(@"*********************************************************");
}

執(zhí)行結(jié)果如下:

2019-04-22 15:02:52.760352+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9826:2087150] CurrentThread begin---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:02:52.760558+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9826:2087150] SyncSerialTask---begin
2019-04-22 15:02:54.761971+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9826:2087150] 1---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:02:56.762653+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9826:2087150] 1---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:02:58.764202+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9826:2087150] 2---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:03:00.765234+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9826:2087150] 2---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:03:02.766464+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9826:2087150] 3---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:03:04.767966+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9826:2087150] 3---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:03:04.768230+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9826:2087150] CurrentThread end---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:03:04.768379+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9826:2087150] SyncSerialTask---end
2019-04-22 15:03:04.768516+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[9826:2087150] *********************************************************

通過我們的代碼測(cè)驗(yàn),可以看出:

  1. 所有的任務(wù)都是在主線程(當(dāng)前線程)中執(zhí)行的,并且是順序執(zhí)行的,沒有開啟新的線程。
  2. 從我們的log中可以看出,我們所有的任務(wù)都是在 beginend之間的,所以說,它會(huì)阻塞當(dāng)前線程,等待隊(duì)列中的任務(wù)執(zhí)行結(jié)束,才會(huì)繼續(xù)執(zhí)行下面的代碼。

異步執(zhí)行+串行隊(duì)列

#pragma mark - 異步執(zhí)行+串行隊(duì)列
/*
 * 特點(diǎn):
 * 1.會(huì)開啟一條新線程
 * 2.按序執(zhí)行任務(wù),執(zhí)行行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)
 * 3.不會(huì)阻塞當(dāng)前線程
 */
- (IBAction)executeAsyncSerialTask:(UIButton *)sender {
    
    NSLog(@"CurrentThread begin---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"AsyncSerialTask---begin");
    
    dispatch_async(self.serialQueue, ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(self.serialQueue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(self.serialQueue, ^{
        // 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    NSLog(@"CurrentThread end---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"AsyncSerialTask---end");
    NSLog(@"*********************************************************");
}

執(zhí)行結(jié)果如下:

2019-04-22 15:25:00.103488+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[10181:2154024] CurrentThread begin---<NSThread: 0x6000019c9400>{number = 1, name = main}
2019-04-22 15:25:00.103734+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[10181:2154024] AsyncSerialTask---begin
2019-04-22 15:25:00.103888+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[10181:2154024] CurrentThread end---<NSThread: 0x6000019c9400>{number = 1, name = main}
2019-04-22 15:25:00.103986+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[10181:2154024] AsyncSerialTask---end
2019-04-22 15:25:00.104091+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[10181:2154024] *********************************************************
2019-04-22 15:25:02.108899+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[10181:2154074] 1---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
2019-04-22 15:25:04.111910+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[10181:2154074] 1---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
2019-04-22 15:25:06.116733+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[10181:2154074] 2---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
2019-04-22 15:25:08.117706+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[10181:2154074] 2---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
2019-04-22 15:25:10.122737+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[10181:2154074] 3---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
2019-04-22 15:25:12.126742+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[10181:2154074] 3---<NSThread: 0x600001992fc0>{number = 4, name = (null)}

通過我們的代碼測(cè)驗(yàn),可以看出:

  1. 三個(gè)追加的任務(wù)都是在一個(gè)新的線程中執(zhí)行的,在串行隊(duì)列中異步執(zhí)行任務(wù),會(huì)開啟一條新線程,由于隊(duì)列是串行的,所以任務(wù)是按序執(zhí)行的。
  2. 從我們的log中可以看到,begin的log之后,馬上就是end的log,因此可以看出,它并不會(huì)阻塞當(dāng)前線程,并不需要等待追加的任務(wù)執(zhí)行完成。

異步執(zhí)行+主隊(duì)列

#pragma mark - 異步執(zhí)行+主隊(duì)列
/*
 * 特點(diǎn):
 * 1.在當(dāng)前線程(主線程)中執(zhí)行任務(wù)
 * 2.按序執(zhí)行任務(wù),執(zhí)行行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)
 * 3.不會(huì)阻塞當(dāng)前線程
 */
- (IBAction)executeAsyncMainQueueTask:(UIButton *)sender {
    
    NSLog(@"CurrentThread begin---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"AsyncMainQueueTask---begin");
    
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    dispatch_async(mainQueue, ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(mainQueue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(mainQueue, ^{
        // 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    NSLog(@"CurrentThread end---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"AsyncMainQueueTask---end");
    NSLog(@"*********************************************************");
}

執(zhí)行結(jié)果如下:

2019-04-22 18:28:03.990381+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[12894:2615623] CurrentThread begin---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:03.990589+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[12894:2615623] AsyncMainQueueTask---begin
2019-04-22 18:28:03.990808+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[12894:2615623] CurrentThread end---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:03.990959+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[12894:2615623] AsyncMainQueueTask---end
2019-04-22 18:28:03.991088+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[12894:2615623] *********************************************************
2019-04-22 18:28:05.993620+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[12894:2615623] 1---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:07.994036+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[12894:2615623] 1---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:09.995547+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[12894:2615623] 2---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:11.997136+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[12894:2615623] 2---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:13.997717+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[12894:2615623] 3---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:15.998158+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[12894:2615623] 3---<NSThread: 0x600003ee6900>{number = 1, name = main}

通過我們的代碼測(cè)驗(yàn),可以看出:

  1. 3個(gè)任務(wù)在主線程中,按序執(zhí)行

  2. 從我們的log中可以看到,begin的log之后,馬上就是end的log,因此可以看出,它并不會(huì)阻塞當(dāng)前線程,并不需要等待追加的任務(wù)執(zhí)行完成。

同步執(zhí)行+主隊(duì)列

#pragma mark - 同步執(zhí)行+主隊(duì)列
/*
 * 特點(diǎn):
 * 會(huì)直接產(chǎn)生死鎖
 */
- (IBAction)executeSyncMainQueueTask:(UIButton *)sender {
    
    NSLog(@"CurrentThread begin---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"SyncMainQueueTask---begin");
    
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    dispatch_sync(mainQueue, ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_sync(mainQueue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_sync(mainQueue, ^{
        // 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    NSLog(@"CurrentThread end---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"SyncMainQueueTask---end");
    NSLog(@"*********************************************************");
}

執(zhí)行結(jié)果如下:

2019-04-22 16:04:57.238644+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[10673:2238846] CurrentThread begin---<NSThread: 0x6000038ca800>{number = 1, name = main}
2019-04-22 16:04:57.238915+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[10673:2238846] SyncMainQueueTask---begin
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
    frame #0: 0x000000010d45fa19 libdispatch.dylib`__DISPATCH_WAIT_FOR_QUEUE__ + 444
    frame #1: 0x00007ffee542deb0
    frame #2: 0x000000010d45f3f0 libdispatch.dylib`_dispatch_sync_f_slow + 231
  * frame #3: 0x000000010a7cff5a GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)`-[ViewController executeSyncMainQueueTask:](self=0x00007fb8f25124f0, _cmd="executeSyncMainQueueTask:", sender=0x00007fb8f2515fa0) at ViewController.m:220
    frame #4: 0x000000010e9b9ecb UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 83
    frame #5: 0x000000010e3f50bd UIKitCore`-[UIControl sendAction:to:forEvent:] + 67
    frame #6: 0x000000010e3f53da UIKitCore`-[UIControl _sendActionsForEvents:withEvent:] + 450
    frame #7: 0x000000010e3f431e UIKitCore`-[UIControl touchesEnded:withEvent:] + 583
    frame #8: 0x000000010e9f50a4 UIKitCore`-[UIWindow _sendTouchesForEvent:] + 2729
    frame #9: 0x000000010e9f67a0 UIKitCore`-[UIWindow sendEvent:] + 4080
    frame #10: 0x000000010e9d4394 UIKitCore`-[UIApplication sendEvent:] + 352
    frame #11: 0x000000010eaa95a9 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 3054
    frame #12: 0x000000010eaac1cb UIKitCore`__handleEventQueueInternal + 5948
    frame #13: 0x000000010bab8721 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #14: 0x000000010bab7f93 CoreFoundation`__CFRunLoopDoSources0 + 243
    frame #15: 0x000000010bab263f CoreFoundation`__CFRunLoopRun + 1263
    frame #16: 0x000000010bab1e11 CoreFoundation`CFRunLoopRunSpecific + 625
    frame #17: 0x00000001141491dd GraphicsServices`GSEventRunModal + 62
    frame #18: 0x000000010e9b881d UIKitCore`UIApplicationMain + 140
    frame #19: 0x000000010a7d03c0 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)`main(argc=1, argv=0x00007ffee542ff90) at main.m:14
    frame #20: 0x000000010d4c7575 libdyld.dylib`start + 1
(lldb) 

通過我們的代碼測(cè)驗(yàn),可以看出:

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
    frame #0: 0x000000010d45fa19 libdispatch.dylib`__DISPATCH_WAIT_FOR_QUEUE__ + 444

應(yīng)用在主線程同步執(zhí)行第一個(gè)任務(wù)時(shí),就會(huì)直接crash,我們同步LLDBbt指定查看函數(shù)調(diào)用棧,可以發(fā)現(xiàn),在系統(tǒng)庫libdispatch調(diào)用__DISPATCH_WAIT_FOR_QUEUE__函數(shù)時(shí),就會(huì)產(chǎn)生一個(gè)由隊(duì)列引起的循環(huán)等待導(dǎo)致的crash,這就是我們常說的Deadlock死鎖,接下里我們來詳細(xì)介紹一下死鎖產(chǎn)生的原因與注意事項(xiàng)。

GCD中產(chǎn)生死鎖的原因

- (IBAction)executeSyncMainQueueTask:(UIButton *)sender {
    
    NSLog(@"CurrentThread begin---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"SyncMainQueueTask---begin");
    
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    dispatch_sync(mainQueue, ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });

從上面的代碼我們可以看出,當(dāng)我們點(diǎn)擊按鈕,調(diào)用executeSyncMainQueueTask方法時(shí),這時(shí)我們其實(shí)是在主隊(duì)列(串行隊(duì)列)提交了一個(gè)任務(wù),我們暫先稱它為任務(wù)0然后我們又使用dispatch_sync同步執(zhí)行方法往主隊(duì)列中提交了任務(wù)1Block,現(xiàn)在我們來分析一下,為什么這種情況下會(huì)產(chǎn)生死鎖

  1. 先往主隊(duì)列(串行隊(duì)列)中提交了任務(wù)0,然后在任務(wù)0執(zhí)行的過程中同步地往主隊(duì)列中添加了任務(wù)1
  2. 主隊(duì)列中添加的任務(wù)都會(huì)在主線程中執(zhí)行,同時(shí)按照串行隊(duì)列的特點(diǎn)(任務(wù)按序執(zhí)行),主線程中首先會(huì)執(zhí)行任務(wù)0,任務(wù)0執(zhí)行完成之后才會(huì)去執(zhí)行任務(wù)1,但是在任務(wù)0執(zhí)行的過程中,使用同步方式往主隊(duì)列中添加任務(wù)1,由于是使用同步方式,這時(shí)主線程會(huì)被阻塞,需要任務(wù)1完成之后,任務(wù)0才會(huì)繼續(xù)往下執(zhí)行。由此,我們可以看出,由于串行隊(duì)列的特性,任務(wù)1會(huì)依賴于任務(wù)0的執(zhí)行完成才會(huì)繼續(xù)往下執(zhí)行,同時(shí)由于同步添加任務(wù)的特性(會(huì)阻塞當(dāng)前線程,直到添加的任務(wù)執(zhí)行完成),任務(wù)0會(huì)依賴于任務(wù)1的執(zhí)行完成。所以,2個(gè)任務(wù)的執(zhí)行就會(huì)因?yàn)橄嗷サ却龑?duì)方的完成,而導(dǎo)致死鎖。

通過上面的分析,我們可以看出這里產(chǎn)生死鎖的一個(gè)很重要的原因就是主隊(duì)列是一個(gè)串行的隊(duì)列(主隊(duì)列中只有一條主線程)。如果我們?nèi)缦吕?,在并發(fā)隊(duì)列中提交,則不會(huì)造成死鎖:

dispatch_async(dispatch_get_global_queue(0, 0), ^{ //這里是為了保證當(dāng)前任務(wù)是處于并發(fā)隊(duì)列開辟的線程中,而不是主線程中
  dispatch_sync(dispatch_get_global_queue(0, 0), ^{
      NSLog(@"任務(wù)0");
  });
  NSLog(@"任務(wù)1");
});

原因是并發(fā)隊(duì)列中的任務(wù)執(zhí)行時(shí)并行的,所以,任務(wù)1并不會(huì)一直等待任務(wù)0執(zhí)行完成,才去執(zhí)行,而是直接執(zhí)行完。因此任務(wù)0因?yàn)槿蝿?wù)1的結(jié)束,線程阻塞也會(huì)被消除,任務(wù)0得以繼續(xù)執(zhí)行。

我們?cè)匍_看一組示例:

//目前處于主線程中
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
      NSLog(@"任務(wù)0");
});
NSLog(@"任務(wù)1");

我們?cè)谥骶€程中,往全局隊(duì)列同步提交了Block,因?yàn)槿株?duì)列和主隊(duì)列是兩個(gè)隊(duì)列,所以任務(wù)1的執(zhí)行,并不需要等待任務(wù)0。所以等任務(wù)0結(jié)束,任務(wù)1也可以被執(zhí)行。
當(dāng)然這里因?yàn)樘峤籅lock所在隊(duì)列,Block被執(zhí)行的隊(duì)列是完全不同的兩個(gè)隊(duì)列,所以這里用串行queue,也是不會(huì)死鎖的,到這里我們也可以知道一些同步提交(dispatch_sync)的阻塞機(jī)制:

同步提交Block,首先是阻塞的當(dāng)前提交Block的線程,而在隊(duì)列中,同步提交的Block,只會(huì)阻塞串行隊(duì)列(由串行隊(duì)列的同一時(shí)間只能執(zhí)行一個(gè)任務(wù)的特性決定),并不會(huì)阻塞并發(fā)隊(duì)列,當(dāng)然dispatch_barrier系列的除外,這個(gè)我會(huì)在后面的文章中講到,歡迎大家繼續(xù)關(guān)注我的博客

現(xiàn)在我們可以用一句話來總結(jié)產(chǎn)生死鎖的原因就是:

使用同步方式(dispatch_sync)提交一個(gè)任務(wù)到一個(gè)串行隊(duì)列時(shí),如果提交這個(gè)任務(wù)的操作所處的線程,也是處于這個(gè)串行隊(duì)列,就會(huì)引起死鎖

開發(fā)中如何避免產(chǎn)生死鎖

  • 不要在主線程中使用同步方式添加任務(wù)到主隊(duì)列
  • 不要嵌套使用在自定義的串行隊(duì)列中,嵌套使用同步方式添加任務(wù)到該串行隊(duì)列

6種組合使用總結(jié)

總結(jié) 串行隊(duì)列 并發(fā)隊(duì)列 主隊(duì)列
同步添加(sync) 不開辟新線程,在當(dāng)前線程中串行執(zhí)行任務(wù) 不開辟新線程,在當(dāng)前線程中串行執(zhí)行任務(wù) 死鎖
異步添加(async) 開辟新線程(1條),串行執(zhí)行任務(wù) 開辟新線程(1/n條),并發(fā)執(zhí)行任務(wù) 不開辟新線程,在主線程中順序執(zhí)行

線程間通信

#pragma mark - 子線程執(zhí)行耗時(shí)代碼,主線程更新UI
- (IBAction)threadInteraction:(UIButton *)sender {
    
    NSLog(@"CurrentThread begin---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"threadInteraction---begin");
    
    //異步添加任務(wù)到全局并發(fā)隊(duì)列執(zhí)行耗時(shí)操作
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        //執(zhí)行耗時(shí)任務(wù)
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
        
        //回到主線程更新UI
        dispatch_sync(dispatch_get_main_queue(), ^{
            
            //Do something here to update UI
            
        });
    });
    
    NSLog(@"CurrentThread end---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"threadInteraction---end");
    NSLog(@"*********************************************************");
}

執(zhí)行結(jié)果如下:

2019-04-22 18:47:54.836027+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[13229:2669381] CurrentThread begin---<NSThread: 0x6000038e9400>{number = 1, name = main}
2019-04-22 18:47:54.836217+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[13229:2669381] threadInteraction---begin
2019-04-22 18:47:54.836436+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[13229:2669381] CurrentThread end---<NSThread: 0x6000038e9400>{number = 1, name = main}
2019-04-22 18:47:54.836619+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[13229:2669381] threadInteraction---end
2019-04-22 18:47:54.836761+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[13229:2669381] *********************************************************
2019-04-22 18:47:56.839416+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[13229:2669427] 1---<NSThread: 0x6000038b6d40>{number = 4, name = (null)}
2019-04-22 18:47:58.840646+0800 GCD(一) 隊(duì)列、任務(wù)、串行、并發(fā)[13229:2669427] 1---<NSThread: 0x6000038b6d40>{number = 4, name = (null)}

在平常的開發(fā)中,我們最常用的就是提交一個(gè)任務(wù)到全局并發(fā)隊(duì)列取執(zhí)行一些比較耗時(shí)的操作(比如,文件下載、文件上傳、圖片解碼、數(shù)據(jù)庫操作、IO讀寫),然后再切回到主線程去更新UI,蘋果官方限定了更新UI的操作只能在主線程中執(zhí)行,所以,我們最后還是要回到主線程去處理我們的UI交互。

本文到這里已經(jīng)基本結(jié)束,在接下來的文章中,我將會(huì)繼續(xù)講解GCD多線程編程的另外幾個(gè)知識(shí)點(diǎn),也是我們平時(shí)開發(fā)中實(shí)際很經(jīng)常會(huì)用到的,如dispatch_barrier、dispatch_group、dispatch_semaphore、線程安全的相關(guān)內(nèi)容

如果文中有錯(cuò)誤的地方,或者與你的想法相悖的地方,請(qǐng)?jiān)谠u(píng)論區(qū)告知我,我會(huì)繼續(xù)改進(jìn),如果你覺得這個(gè)篇文章總結(jié)的還不錯(cuò),麻煩動(dòng)動(dòng)小手,給我的文章與Git代碼樣例點(diǎn)個(gè)?

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • iOS多線程編程 基本知識(shí) 1. 進(jìn)程(process) 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,就是一段程序的執(zhí)...
    陵無山閱讀 6,364評(píng)論 1 14
  • 本文用來介紹 iOS 多線程中 GCD 的相關(guān)知識(shí)以及使用方法。這大概是史上最詳細(xì)、清晰的關(guān)于 GCD 的詳細(xì)講...
    花花世界的孤獨(dú)行者閱讀 582評(píng)論 0 1
  • 從哪說起呢? 單純講多線程編程真的不知道從哪下嘴。。 不如我直接引用一個(gè)最簡單的問題,以這個(gè)作為切入點(diǎn)好了 在ma...
    Mr_Baymax閱讀 2,917評(píng)論 1 17
  • 夢(mèng)凝姿倒吸一口冷氣:“怎么可以這樣?!” 夢(mèng)華眉頭皺了皺,又圍著兩具失去靈魂的軀體轉(zhuǎn)了兩圈,拉起夢(mèng)凝姿的手,“走,...
    河許人閱讀 1,548評(píng)論 14 76
  • 作為一個(gè)二年級(jí)孩子的媽媽,很多時(shí)候心情隨著孩子的學(xué)習(xí)成績一層一層地波動(dòng)著。當(dāng)學(xué)了一些心理學(xué)和教育理念,慢慢發(fā)現(xiàn),其...
    金丹鳳的作坊閱讀 398評(píng)論 0 0

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