GCD(一) 隊列、任務(wù)洽腺、串行脚粟、并發(fā)

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

  • 多線程的幾個基本概念:進(jìn)程與線程核无、串行與并發(fā)
  • GCD中的2個核心內(nèi)容:隊列任務(wù)
  • GCD的基本使用步驟
  • GCD中使用同步異步方式添加任務(wù)到串行并發(fā)隊列后執(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ā)的一個多核編程的較新的解決方法,它主要用于優(yōu)化應(yīng)用程序以支持多核處理器,它是一個在線程池模式的基礎(chǔ)上執(zhí)行的并發(fā)任務(wù),是我們平常開發(fā)中最常見的一種多線程編程方式

測試代碼在這

多線程基本概念

進(jìn)程與線程

對于操作系統(tǒng)來說团南,一個任務(wù)就是一個進(jìn)程(Process),比如打開一個瀏覽器就是啟動一個瀏覽器進(jìn)程堕担,打開一個記事本就啟動了一個記事本進(jìn)程已慢,打開兩個記事本就啟動了兩個記事本進(jìn)程曲聂,打開一個Word就啟動了一個Word進(jìn)程霹购。

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

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

串行和并發(fā)

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

串行就是任務(wù)一個一個的執(zhí)行饿自,時間上不相互重疊汰翠,一個任務(wù)執(zhí)行結(jié)束龄坪,下一個任務(wù)才能開始執(zhí)行。

GCG隊列

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

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

GCD中的隊列可以分為以下2種:

  • 串行隊列 ( Serial Dispatch Queue )

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

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

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

在我們平時的開發(fā)中向楼,還有2種我們最常見的加勤,也是使用頻率最高的隊列:

  • 主隊列 ( Main Dispatch Queue )

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

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

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

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

GCD任務(wù)

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

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

  • 同步執(zhí)行

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

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

  • 異步執(zhí)行

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

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

GCD的使用步驟

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

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

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

GCD使用步驟

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

創(chuàng)建或獲取一個隊列

  • 使用dispatch_get_main_queue() 獲取主隊列沪悲。

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

    Reserved for future use. Passing any value other than zero may result in a NULL return value.
    
  • 使用dispatch_queue_create函數(shù)烂叔,創(chuàng)建自定義的串行或并行隊列,這個函數(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ù)最后返回了一個隊列dispatch_queue_t,這個函數(shù)有2個參數(shù)固歪,第一個參數(shù)其實(shí)可以看成是是我們給這個隊列取的名字蒜鸡,以便后續(xù)debug,蘋果官方是推薦開發(fā)者使用逆序全程域名。

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

    /*!
     * @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
    

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

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

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

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

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

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

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

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

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

GCD的基本使用

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

三個角度

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

六種組合方式

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

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

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

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

2019-04-22 13:42:07.265811+0800 GCD(一) 隊列莫湘、任務(wù)尤蒿、串行、并發(fā)[8668:1868026] CurrentThread---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:07.265945+0800 GCD(一) 隊列幅垮、任務(wù)腰池、串行、并發(fā)[8668:1868026] SyncConcurrencyTask---begin
2019-04-22 13:42:09.266681+0800 GCD(一) 隊列忙芒、任務(wù)示弓、串行、并發(fā)[8668:1868026] 1---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:11.268049+0800 GCD(一) 隊列呵萨、任務(wù)奏属、串行、并發(fā)[8668:1868026] 1---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:12.299360+0800 GCD(一) 隊列潮峦、任務(wù)囱皿、串行、并發(fā)[8668:1868133] XPC connection interrupted
2019-04-22 13:42:13.269544+0800 GCD(一) 隊列忱嘹、任務(wù)嘱腥、串行、并發(fā)[8668:1868026] 2---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:15.270540+0800 GCD(一) 隊列拘悦、任務(wù)齿兔、串行、并發(fā)[8668:1868026] 2---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:17.271936+0800 GCD(一) 隊列础米、任務(wù)分苇、串行、并發(fā)[8668:1868026] 3---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:19.273478+0800 GCD(一) 隊列椭盏、任務(wù)组砚、串行、并發(fā)[8668:1868026] 3---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:19.273713+0800 GCD(一) 隊列掏颊、任務(wù)糟红、串行艾帐、并發(fā)[8668:1868026] SyncConcurrencyTask---end
2019-04-22 13:42:19.273857+0800 GCD(一) 隊列、任務(wù)盆偿、串行柒爸、并發(fā)[8668:1868026] *********************************************************

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

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

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

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

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

#pragma mark - 異步執(zhí)行+并發(fā)隊列
/*
 * 特點(diǎn):
 * 1.開啟多個新線程執(zhí)行任務(wù)
 * 2.任務(wù)交替(同時)執(zhí)行
 * 3.不會阻塞當(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];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(self.concurrentQueue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(self.concurrentQueue, ^{
        // 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            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(一) 隊列或南、任務(wù)、串行艾君、并發(fā)[9417:2009244] CurrentThread begin---<NSThread: 0x6000000cea40>{number = 1, name = main}
2019-04-22 14:32:14.941405+0800 GCD(一) 隊列、任務(wù)肄方、串行冰垄、并發(fā)[9417:2009244] AsyncConcurrencyTask---begin
2019-04-22 14:32:14.941608+0800 GCD(一) 隊列、任務(wù)权她、串行虹茶、并發(fā)[9417:2009244] CurrentThread end---<NSThread: 0x6000000cea40>{number = 1, name = main}
2019-04-22 14:32:14.941757+0800 GCD(一) 隊列、任務(wù)隅要、串行蝴罪、并發(fā)[9417:2009244] AsyncConcurrencyTask---end
2019-04-22 14:32:14.941894+0800 GCD(一) 隊列、任務(wù)步清、串行要门、并發(fā)[9417:2009244] *********************************************************
2019-04-22 14:32:16.945860+0800 GCD(一) 隊列虏肾、任務(wù)、串行欢搜、并發(fā)[9417:2009294] 1---<NSThread: 0x6000000a2380>{number = 4, name = (null)}
2019-04-22 14:32:16.945909+0800 GCD(一) 隊列封豪、任務(wù)、串行炒瘟、并發(fā)[9417:2011461] 3---<NSThread: 0x6000000a2340>{number = 6, name = (null)}
2019-04-22 14:32:16.945909+0800 GCD(一) 隊列吹埠、任務(wù)、串行疮装、并發(fā)[9417:2011460] 2---<NSThread: 0x6000000aec00>{number = 5, name = (null)}
2019-04-22 14:32:18.951120+0800 GCD(一) 隊列缘琅、任務(wù)、串行廓推、并發(fā)[9417:2011460] 2---<NSThread: 0x6000000aec00>{number = 5, name = (null)}
2019-04-22 14:32:18.951121+0800 GCD(一) 隊列刷袍、任務(wù)、串行受啥、并發(fā)[9417:2011461] 3---<NSThread: 0x6000000a2340>{number = 6, name = (null)}
2019-04-22 14:32:18.951120+0800 GCD(一) 隊列做个、任務(wù)、串行滚局、并發(fā)[9417:2009294] 1---<NSThread: 0x6000000a2380>{number = 4, name = (null)}

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

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

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

#pragma mark - 同步執(zhí)行+串行隊列
/*
 * 特點(diǎn):
 * 1.在當(dāng)前線程中執(zhí)行任務(wù)涨缚,不會開啟新線程
 * 2.按序執(zhí)行任務(wù)轧粟,執(zhí)行行完一個任務(wù),再執(zhí)行下一個任務(wù)
 * 3.會阻塞當(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];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_sync(self.serialQueue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_sync(self.serialQueue, ^{
        // 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            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(一) 隊列脓魏、任務(wù)兰吟、串行、并發(fā)[9826:2087150] CurrentThread begin---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:02:52.760558+0800 GCD(一) 隊列茂翔、任務(wù)混蔼、串行、并發(fā)[9826:2087150] SyncSerialTask---begin
2019-04-22 15:02:54.761971+0800 GCD(一) 隊列珊燎、任務(wù)惭嚣、串行遵湖、并發(fā)[9826:2087150] 1---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:02:56.762653+0800 GCD(一) 隊列、任務(wù)料按、串行奄侠、并發(fā)[9826:2087150] 1---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:02:58.764202+0800 GCD(一) 隊列、任務(wù)载矿、串行垄潮、并發(fā)[9826:2087150] 2---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:03:00.765234+0800 GCD(一) 隊列、任務(wù)闷盔、串行弯洗、并發(fā)[9826:2087150] 2---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:03:02.766464+0800 GCD(一) 隊列、任務(wù)逢勾、串行牡整、并發(fā)[9826:2087150] 3---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:03:04.767966+0800 GCD(一) 隊列、任務(wù)溺拱、串行逃贝、并發(fā)[9826:2087150] 3---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:03:04.768230+0800 GCD(一) 隊列、任務(wù)迫摔、串行沐扳、并發(fā)[9826:2087150] CurrentThread end---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:03:04.768379+0800 GCD(一) 隊列、任務(wù)句占、串行沪摄、并發(fā)[9826:2087150] SyncSerialTask---end
2019-04-22 15:03:04.768516+0800 GCD(一) 隊列、任務(wù)纱烘、串行杨拐、并發(fā)[9826:2087150] *********************************************************

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

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

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

#pragma mark - 異步執(zhí)行+串行隊列
/*
 * 特點(diǎn):
 * 1.會開啟一條新線程
 * 2.按序執(zhí)行任務(wù)塌碌,執(zhí)行行完一個任務(wù),再執(zhí)行下一個任務(wù)
 * 3.不會阻塞當(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];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(self.serialQueue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(self.serialQueue, ^{
        // 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            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(一) 隊列旬盯、任務(wù)台妆、串行翎猛、并發(fā)[10181:2154024] CurrentThread begin---<NSThread: 0x6000019c9400>{number = 1, name = main}
2019-04-22 15:25:00.103734+0800 GCD(一) 隊列、任務(wù)接剩、串行切厘、并發(fā)[10181:2154024] AsyncSerialTask---begin
2019-04-22 15:25:00.103888+0800 GCD(一) 隊列、任務(wù)懊缺、串行疫稿、并發(fā)[10181:2154024] CurrentThread end---<NSThread: 0x6000019c9400>{number = 1, name = main}
2019-04-22 15:25:00.103986+0800 GCD(一) 隊列、任務(wù)鹃两、串行遗座、并發(fā)[10181:2154024] AsyncSerialTask---end
2019-04-22 15:25:00.104091+0800 GCD(一) 隊列、任務(wù)俊扳、串行途蒋、并發(fā)[10181:2154024] *********************************************************
2019-04-22 15:25:02.108899+0800 GCD(一) 隊列、任務(wù)馋记、串行号坡、并發(fā)[10181:2154074] 1---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
2019-04-22 15:25:04.111910+0800 GCD(一) 隊列、任務(wù)梯醒、串行宽堆、并發(fā)[10181:2154074] 1---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
2019-04-22 15:25:06.116733+0800 GCD(一) 隊列、任務(wù)冤馏、串行日麸、并發(fā)[10181:2154074] 2---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
2019-04-22 15:25:08.117706+0800 GCD(一) 隊列代箭、任務(wù)、串行嗡综、并發(fā)[10181:2154074] 2---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
2019-04-22 15:25:10.122737+0800 GCD(一) 隊列杜漠、任務(wù)、串行驾茴、并發(fā)[10181:2154074] 3---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
2019-04-22 15:25:12.126742+0800 GCD(一) 隊列盼樟、任務(wù)、串行锈至、并發(fā)[10181:2154074] 3---<NSThread: 0x600001992fc0>{number = 4, name = (null)}

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

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

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

#pragma mark - 異步執(zhí)行+主隊列
/*
 * 特點(diǎn):
 * 1.在當(dāng)前線程(主線程)中執(zhí)行任務(wù)
 * 2.按序執(zhí)行任務(wù),執(zhí)行行完一個任務(wù)殊者,再執(zhí)行下一個任務(wù)
 * 3.不會阻塞當(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];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(mainQueue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(mainQueue, ^{
        // 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            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(一) 隊列与境、任務(wù)、串行猖吴、并發(fā)[12894:2615623] CurrentThread begin---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:03.990589+0800 GCD(一) 隊列摔刁、任務(wù)、串行海蔽、并發(fā)[12894:2615623] AsyncMainQueueTask---begin
2019-04-22 18:28:03.990808+0800 GCD(一) 隊列共屈、任務(wù)、串行拗引、并發(fā)[12894:2615623] CurrentThread end---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:03.990959+0800 GCD(一) 隊列矾削、任務(wù)哼凯、串行断部、并發(fā)[12894:2615623] AsyncMainQueueTask---end
2019-04-22 18:28:03.991088+0800 GCD(一) 隊列蝴光、任務(wù)、串行沉唠、并發(fā)[12894:2615623] *********************************************************
2019-04-22 18:28:05.993620+0800 GCD(一) 隊列、任務(wù)纱扭、串行暗赶、并發(fā)[12894:2615623] 1---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:07.994036+0800 GCD(一) 隊列蹂随、任務(wù)、串行激率、并發(fā)[12894:2615623] 1---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:09.995547+0800 GCD(一) 隊列乒躺、任務(wù)、串行讳推、并發(fā)[12894:2615623] 2---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:11.997136+0800 GCD(一) 隊列娜遵、任務(wù)、串行纳胧、并發(fā)[12894:2615623] 2---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:13.997717+0800 GCD(一) 隊列跑慕、任務(wù)牢硅、串行、并發(fā)[12894:2615623] 3---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:15.998158+0800 GCD(一) 隊列位岔、任務(wù)抒抬、串行擦剑、并發(fā)[12894:2615623] 3---<NSThread: 0x600003ee6900>{number = 1, name = main}

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

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

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

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

#pragma mark - 同步執(zhí)行+主隊列
/*
 * 特點(diǎn):
 * 會直接產(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];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_sync(mainQueue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_sync(mainQueue, ^{
        // 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            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(一) 隊列邦邦、任務(wù)、串行、并發(fā)[10673:2238846] CurrentThread begin---<NSThread: 0x6000038ca800>{number = 1, name = main}
2019-04-22 16:04:57.238915+0800 GCD(一) 隊列、任務(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(一) 隊列倒得、任務(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(一) 隊列秉馏、任務(wù)萝究、串行琴昆、并發(fā)`main(argc=1, argv=0x00007ffee542ff90) at main.m:14
    frame #20: 0x000000010d4c7575 libdyld.dylib`start + 1
(lldb) 

通過我們的代碼測驗(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í)行第一個任務(wù)時,就會直接crash,我們同步LLDBbt指定查看函數(shù)調(diào)用棧,可以發(fā)現(xiàn)舷暮,在系統(tǒng)庫libdispatch調(diào)用__DISPATCH_WAIT_FOR_QUEUE__函數(shù)時态罪,就會產(chǎn)生一個由隊列引起的循環(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];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });

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

  1. 先往主隊列(串行隊列)中提交了任務(wù)0侦厚,然后在任務(wù)0執(zhí)行的過程中同步地往主隊列中添加了任務(wù)1
  2. 主隊列中添加的任務(wù)都會在主線程中執(zhí)行岛心,同時按照串行隊列的特點(diǎn)(任務(wù)按序執(zhí)行)送朱,主線程中首先會執(zhí)行任務(wù)0大年,任務(wù)0執(zhí)行完成之后才會去執(zhí)行任務(wù)1,但是在任務(wù)0執(zhí)行的過程中,使用同步方式往主隊列中添加任務(wù)1碑定,由于是使用同步方式,這時主線程會被阻塞审编,需要任務(wù)1完成之后,任務(wù)0才會繼續(xù)往下執(zhí)行券时。由此适肠,我們可以看出辩稽,由于串行隊列的特性并级,任務(wù)1會依賴于任務(wù)0的執(zhí)行完成才會繼續(xù)往下執(zhí)行嘲碧,同時由于同步添加任務(wù)的特性(會阻塞當(dāng)前線程履婉,直到添加的任務(wù)執(zhí)行完成)胯究,任務(wù)0會依賴于任務(wù)1的執(zhí)行完成督暂。所以,2個任務(wù)的執(zhí)行就會因?yàn)橄嗷サ却龑Ψ降耐瓿烧鴮?dǎo)致死鎖管引。

通過上面的分析,我們可以看出這里產(chǎn)生死鎖的一個很重要的原因就是主隊列是一個串行的隊列(主隊列中只有一條主線程)闯两。如果我們?nèi)缦吕彀椋诓l(fā)隊列中提交谅将,則不會造成死鎖:

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

原因是并發(fā)隊列中的任務(wù)執(zhí)行時并行的重慢,所以饥臂,任務(wù)1并不會一直等待任務(wù)0執(zhí)行完成,才去執(zhí)行似踱,而是直接執(zhí)行完隅熙。因此任務(wù)0因?yàn)槿蝿?wù)1的結(jié)束,線程阻塞也會被消除屯援,任務(wù)0得以繼續(xù)執(zhí)行猛们。

我們再開看一組示例:

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

我們在主線程中,往全局隊列同步提交了Block狞洋,因?yàn)槿株犃泻椭麝犃惺莾蓚€隊列弯淘,所以任務(wù)1的執(zhí)行,并不需要等待任務(wù)0吉懊。所以等任務(wù)0結(jié)束庐橙,任務(wù)1也可以被執(zhí)行。
當(dāng)然這里因?yàn)樘峤籅lock所在隊列借嗽,Block被執(zhí)行的隊列是完全不同的兩個隊列态鳖,所以這里用串行queue,也是不會死鎖的恶导,到這里我們也可以知道一些同步提交(dispatch_sync)的阻塞機(jī)制:

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

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

使用同步方式(dispatch_sync)提交一個任務(wù)到一個串行隊列時顺囊,如果提交這個任務(wù)的操作所處的線程,也是處于這個串行隊列蕉拢,就會引起死鎖

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

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

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

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

線程間通信

#pragma mark - 子線程執(zhí)行耗時代碼硅瞧,主線程更新UI
- (IBAction)threadInteraction:(UIButton *)sender {
    
    NSLog(@"CurrentThread begin---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"threadInteraction---begin");
    
    //異步添加任務(wù)到全局并發(fā)隊列執(zhí)行耗時操作
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        //執(zhí)行耗時任務(wù)
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            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(一) 隊列、任務(wù)恕汇、串行腕唧、并發(fā)[13229:2669381] CurrentThread begin---<NSThread: 0x6000038e9400>{number = 1, name = main}
2019-04-22 18:47:54.836217+0800 GCD(一) 隊列或辖、任務(wù)、串行枣接、并發(fā)[13229:2669381] threadInteraction---begin
2019-04-22 18:47:54.836436+0800 GCD(一) 隊列颂暇、任務(wù)、串行但惶、并發(fā)[13229:2669381] CurrentThread end---<NSThread: 0x6000038e9400>{number = 1, name = main}
2019-04-22 18:47:54.836619+0800 GCD(一) 隊列耳鸯、任務(wù)、串行膀曾、并發(fā)[13229:2669381] threadInteraction---end
2019-04-22 18:47:54.836761+0800 GCD(一) 隊列县爬、任務(wù)、串行添谊、并發(fā)[13229:2669381] *********************************************************
2019-04-22 18:47:56.839416+0800 GCD(一) 隊列财喳、任務(wù)、串行斩狱、并發(fā)[13229:2669427] 1---<NSThread: 0x6000038b6d40>{number = 4, name = (null)}
2019-04-22 18:47:58.840646+0800 GCD(一) 隊列耳高、任務(wù)、串行所踊、并發(fā)[13229:2669427] 1---<NSThread: 0x6000038b6d40>{number = 4, name = (null)}

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

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

如果文中有錯誤的地方截歉,或者與你的想法相悖的地方,請在評論區(qū)告知我烟零,我會繼續(xù)改進(jìn)瘪松,如果你覺得這個篇文章總結(jié)的還不錯咸作,麻煩動動小手,給我的文章與Git代碼樣例點(diǎn)個?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宵睦,一起剝皮案震驚了整個濱河市记罚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌壳嚎,老刑警劉巖桐智,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異烟馅,居然都是意外死亡说庭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門郑趁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刊驴,“玉大人,你說我怎么就攤上這事穿撮∪甭觯” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵悦穿,是天一觀的道長攻礼。 經(jīng)常有香客問我,道長栗柒,這世上最難降的妖魔是什么礁扮? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮瞬沦,結(jié)果婚禮上太伊,老公的妹妹穿的比我還像新娘。我一直安慰自己逛钻,他們只是感情好僚焦,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著曙痘,像睡著了一般芳悲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上边坤,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天名扛,我揣著相機(jī)與錄音,去河邊找鬼茧痒。 笑死毫别,一個胖子當(dāng)著我的面吹牛哪怔,可吹牛的內(nèi)容都是我干的悍赢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼超燃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了桩蓉?” 一聲冷哼從身側(cè)響起淋纲,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎院究,沒想到半個月后洽瞬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡业汰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年伙窃,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片样漆。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡为障,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出放祟,到底是詐尸還是另有隱情鳍怨,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布跪妥,位于F島的核電站鞋喇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏眉撵。R本人自食惡果不足惜侦香,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纽疟。 院中可真熱鬧罐韩,春花似錦、人聲如沸污朽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蟆肆。三九已至矾睦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間颓芭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工柬赐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留亡问,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像州藕,于是被迫代替她去往敵國和親束世。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

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