多線程第一彈 - GCD

前言

在開發(fā)過程中,我們會經(jīng)常和網(wǎng)絡(luò)打交道,與網(wǎng)絡(luò)相關(guān)的知識無疑是iOS開發(fā)中的難點之一,學(xué)習(xí)多線程,我走了很多彎路,遇到過很多坑,不過對于我們來說,學(xué)習(xí)就是需要從一個坑爬出來,然后又跳到另一個坑.本篇文章主要介紹多線程相關(guān)的基本知識,想要深入了解,可以關(guān)注CocoaChina微信公眾號,里面有很多大牛的技術(shù)博客.感謝@小笨狼Lc提供的相關(guān)資料.接下來我們正式開始我們的多線程之旅吧.

在正式接觸多線程的核心知識前,我們需要了解一些與網(wǎng)絡(luò)相關(guān)的基本知識.比如說:進(jìn)程和線程分別代表的是什么意思. 兩者有什么聯(lián)系等.

進(jìn)程與線程的基本概念

  • 什么是進(jìn)程 : 所謂的進(jìn)程指的就是系統(tǒng)在運行過程中正在運行的一個應(yīng)用程序.
  • 進(jìn)程的特點 : iOS系統(tǒng)中進(jìn)程之間是相互獨立的,也就是說,每個進(jìn)程都是在其受保護(hù)的內(nèi)存空間中運行的.這就是為什么iOS系統(tǒng)用起來比某些系統(tǒng)(沒有特指)更加的流暢的原因.下面我們根據(jù)一副圖來說明iOS中進(jìn)程的特點.
    在我們的Mac電腦上同時打開快播和迅雷兩款軟件,從圖片中可以看出,他們之間是沒有聯(lián)系的,兩兩之間是相互獨立的.我們可以通過Mac電腦上的"活動監(jiān)視器"來查看所有正在開啟的進(jìn)程
進(jìn)程.png
  • 什么是線程 :一個進(jìn)程(后面我們直接就說成應(yīng)用程序)想要執(zhí)行任務(wù),那么它必須要有線程,至少要有一條線程,原因是應(yīng)用程序中的所有的任務(wù)都是在線程上執(zhí)行的,也就是說,沒有線程就不能執(zhí)行任務(wù).

  • 比如根據(jù)下面的圖片,可以看出當(dāng)我們開啟QQ音樂和快播兩款軟件時需要一條對應(yīng)的線程,并且如果我們想要播放音樂或者是下載電影(0),那么他們也要有一條與之對應(yīng)的線程.總的來說,只要應(yīng)用程序想要執(zhí)行某項任務(wù),那么它就必須要有線程,至少要有一條.注意:播放音樂的線程和下載電影的線程之間是相互獨立的.

線程.png

線程的串行與并行

  • 線程串行 : 在同一條線程上執(zhí)行所有的任務(wù),這種運行方式就叫做串行.
  • 串行的特點 : 如果一個進(jìn)程執(zhí)行任務(wù)的方式是串行執(zhí)行,那么就表示該進(jìn)程上的所有任務(wù)都是在同一條線程上執(zhí)行的,并且他們的執(zhí)行方式是按順序執(zhí)行的,也就是說,在同時間,一條線程只能執(zhí)行一個任務(wù),只有當(dāng)當(dāng)前任務(wù)執(zhí)行完畢之后,才會去繼續(xù)執(zhí)行下一個任務(wù)(執(zhí)行的任務(wù)是有序的).
  • 比如下面的圖片:只有一條線程(黑色箭頭).我們需要下載A, B, C三個文件,這時候的執(zhí)行順序是,只有當(dāng)"A文件下載"完畢之后,才會去執(zhí)行"下載文件B"操作,最后當(dāng)"下載文件B"執(zhí)行完畢之后,才去執(zhí)行"下載文件C".
串行執(zhí)行.png
  • 什么是多線程 : 在同一個進(jìn)程中,擁有多條線程,并行執(zhí)行多個任務(wù)(關(guān)于多線程,下面會有更加詳細(xì)的介紹,這里只是其拋磚引玉的作用).
  • 什么是并行 : 多條線程同時執(zhí)行多個任務(wù)(執(zhí)行的任務(wù)是無序的)比如說下面的圖片中,開啟了三條線程(黑色箭頭)分別執(zhí)行下載對應(yīng)的文件,就是說:在同一個進(jìn)程中開啟3條線程,同時執(zhí)行下載操作,實現(xiàn)ABC三個文件同時下載.
并行執(zhí)行.png
  • 注意 : 并行執(zhí)行的本質(zhì)并不是真正意義上的同時執(zhí)行,那只是給用戶視覺上的錯覺.它的實質(zhì)是在同一時間刻也是只能執(zhí)行一個任務(wù).它只是線程之間高速切換,給人一種同時下載資源的錯覺.(知道就可以了,不必較真兒)

多線程

  • 什么是多線程 : 在一個進(jìn)程中可以開啟多條線程,同時執(zhí)行多個任務(wù).
  • 使用多線程的優(yōu)缺點
  • 1, 多線程的優(yōu)點
    • 1.1, 在一定程度上可以提高程序的執(zhí)行效率
    • 1.2, 能夠適當(dāng)提高資源的利用率(比如說:CPU, 內(nèi)存等利用率)
  • 2, 多線程的缺點
    • 2.1, 創(chuàng)建線程是有一定的開銷的,如果開啟大量的線程的話,肯定會降低程序的性能.
    • 2.2, 線程開啟的越多,CPU在調(diào)度線程上的開銷就越大
    • 2.3, 程序的設(shè)計變得更加復(fù)雜,比如線程間的通信(下面會詳細(xì)介紹),又或者多線程的數(shù)據(jù)共享

iOS中多線程的實現(xiàn)方案

  • pthread : 一套通用的多線程并且基于C語言的API,由程序員來管理線程的生命周期,使用難度很大(本篇文章就不介紹了)
  • NSThread : 相對于pthread,它更加面向?qū)ο?使用起來相對簡單,生命周期由程序員來管理,但是在開發(fā)中一般不用,使用場景比如說:我想要知道當(dāng)前線程是主線程還是子線程([NSThread currentThread])
  • GCD : 是對NSThread進(jìn)一步的封裝,使用起來更加建檔方便快捷,主要是它使用了block代碼塊.是一套基于C的API,生命周期由系統(tǒng)自動管理.
  • NSOperation : 是對GCD進(jìn)一步的封裝,底層是GCD,相對GCD來說,使用更加面向?qū)ο?線程的命周期也是由系統(tǒng)自動管理.

GCD

什么是GCD : GCD是Grand Central Disparch的簡稱,GCD可以自動管理線程的生命周期(創(chuàng)建線程, 調(diào)度線程以及銷毀線程).所以我們不需要再去關(guān)注線程,只需要高數(shù)GCD執(zhí)行的是什么任務(wù)以及以什么方式執(zhí)行任務(wù)即可.

GCD的兩個核心概念(任務(wù)和隊列)

  • 任務(wù): 所謂的任務(wù)就是我們想要執(zhí)行的代碼,換句話說就是block代碼塊中的代碼又或者是一個指針函數(shù).
  • 隊列 : 可以將隊列比喻成存放任務(wù)的容器

GCD的兩個核心步驟:

  • 制定任務(wù) : 即想要實現(xiàn)什么效果的代碼
  • 添加任務(wù) : 即將確定的任務(wù)添加到隊列中,GCD會自動將添加的任務(wù)取出來,放到對應(yīng)線程上去執(zhí)行.這里涉及到任務(wù)進(jìn)出一個原則:FIFO(先進(jìn)先出)
先進(jìn)先出原則.png
  • dispatch_queue_t queue
    對象就有內(nèi)存。跟普通OC對象類似拙毫,我們可以用dispatch_retain()和dispatch_release()對其進(jìn)行內(nèi)存管理依许,當(dāng)一個任務(wù)加入到一個queue中的時候,任務(wù)會retain這個queue缀蹄,直到任務(wù)執(zhí)行完成才會release峭跳。
    在iOS 6之后,dispatch對象已經(jīng)支持ARC缺前,所以在ARC工程之下蛀醉,我們可以不用擔(dān)心他的內(nèi)存是否被釋放.

  • 隊列的知識補(bǔ)充:要申明一個dispatch的屬性。一般情況下我們只需要用strong即可衅码。

@property (nonatomic, strong) dispatch_queue_t queue;
  • 注意:如果你是寫一個framework拯刁,framework的使用者的SDK有可能還是古董級的iOS6之前。那么你需要根據(jù)OS_OBJECT_USE_OBJC做一個判斷是使用strong還是assign逝段。
#if OS_OBJECT_USE_OBJC
@property (nonatomic, strong) dispatch_queue_t queue;
#else
@property (nonatomic, assign) dispatch_queue_t queue;
#endif

GCD中的同步函數(shù)與異步函數(shù)

異步函數(shù)(async) : 可以在新線程上執(zhí)行任務(wù)具備開啟新線程的能力.
// 參數(shù)傳的是一個block
dispatch_async(dispatch_queue_t queue, ^(void)block);

// 參數(shù)傳的是一個指針函數(shù)
dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
  • 兩個異步執(zhí)行的API實現(xiàn)的功能是一樣的,都是講任務(wù)提交到指定的隊列中,提交后就立即返回,不用等待任務(wù)的執(zhí)行完畢. 提交之后系統(tǒng)內(nèi)部會對隊列queue進(jìn)行一次retain操作,等到任務(wù)執(zhí)行完畢之后,隊列queue再被release一次.它們之間唯一的區(qū)別是最后一個參數(shù)不同,前者是接收block為參數(shù),后者是接收函數(shù)為參數(shù).

  • **注意(重點:面試可能會被問到) :多線程方法的block中為什么能使用self,不會造成循環(huán)引用嗎?
    **

  • 答: 首先肯定是不會有循環(huán)引用的,用不著(__weak typeof(self) weakSelf = self).原因:是當(dāng)使用ispatch_async的時候block會被copy(深拷貝)一次,雖然當(dāng)block執(zhí)行任務(wù)完成之后block會被release,但是此時的系統(tǒng)已經(jīng)擁有了之前拷貝的block了,所以用不著擔(dān)心循環(huán)引用問題,也就是說,block中的self不用將之弱化,直接使用即可(注意:這里只是特例,如果沒有copy那么還是需要將block里面的self變?yōu)閣eakSelf)

  • 異步是一個比較抽象的概念垛玻,簡單的說就是將任務(wù)加入到隊列中之后,立即返回奶躯,不需要等待任務(wù)的執(zhí)行.我們用代碼加深一下對概念的理解

異步函數(shù)示例
- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"異步函數(shù)之前 %@",[NSThread currentThread]);
    /**
     * 參數(shù) 1: 隊列的名稱  參數(shù) 2: 隊列的類型
     * DISPATCH_QUEUE_SERIAL NULL  // 表示串行隊列
     * DISPATCH_QUEUE_CONCURRENT  // 表示并發(fā)隊列
     */
    dispatch_queue_t queue = dispatch_queue_create("我的微博是@WilliamAlex大叔", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{

        NSLog(@"異步函數(shù)中%@",[NSThread currentThread]);
    });

    NSLog(@"異步函數(shù)之后 %@",[NSThread currentThread]);
}
打印結(jié)果
2016-03-21 21:31:17.349 01 - 多線程[998:85748] 異步函數(shù)之前 <NSThread: 0x7fe800707430>{number = 1, name = main}
2016-03-21 21:31:17.350 01 - 多線程[998:85748] 異步函數(shù)之后 <NSThread: 0x7fe800707430>{number = 1, name = main}
2016-03-21 21:31:17.350 01 - 多線程[998:85848] 異步函數(shù)中<NSThread: 0x7fe80077c100>{number = 2, name = (null)}
  • 結(jié)論 : 當(dāng)使用異步函數(shù)時,它只是將任務(wù)放置在隊列中而已,并不需要等待任務(wù)的執(zhí)行,當(dāng)將任務(wù)提交到隊列中時,就會立即返回,根據(jù)打印結(jié)果可以看出異步函數(shù)中的打印是最后才打印的.也就是說,使用異步函數(shù)是不會造成線程死鎖等問題.
同步函數(shù) : 只能在當(dāng)前線程上執(zhí)行任務(wù),不具備開啟子線程的能力.
// 參數(shù)是block
dispatch_sync(dispatch_queue_t queue, ^(void)block);

// 參數(shù)是函數(shù)
dispatch_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

  • 兩個方法的作用完全是相同的,都是講任務(wù)提交到queue中,任務(wù)添加到隊列中后,不會立即返回,必須要等待任務(wù)執(zhí)行結(jié)束之后再返回,他們的唯一的區(qū)別就是最后一個接收的參數(shù)不一樣,一個是接收block代碼塊,一個接收的是指針函數(shù).

  • 同步表示任務(wù)加入到隊列中之后不會立即返回帚桩,等待任務(wù)完成再返回。語言的描述比較抽象嘹黔,我們再次用代碼加深一下對概念的理解

同步函數(shù)示例

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"同步函數(shù)之前 %@",[NSThread currentThread]);
    /**
     * 參數(shù) 1: 隊列的名稱  參數(shù) 2: 隊列的類型
     * DISPATCH_QUEUE_SERIAL NULL  // 表示串行隊列
     * DISPATCH_QUEUE_CONCURRENT  // 表示并發(fā)隊列
     */
    dispatch_queue_t queue = dispatch_queue_create("我的微博是@WilliamAlex大叔", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(queue, ^{

        NSLog(@"同步函數(shù)中%@",[NSThread currentThread]);
    });

    NSLog(@"同步函數(shù)之后 %@",[NSThread currentThread]);
}
打印結(jié)果
2016-03-21 22:23:28.534 01 - 多線程[1037:95068] 同步函數(shù)之前 <NSThread: 0x7f8fd2601920>{number = 1, name = main}
2016-03-21 22:23:28.535 01 - 多線程[1037:95068] 同步函數(shù)中<NSThread: 0x7f8fd2601920>{number = 1, name = main}
2016-03-21 22:23:28.535 01 - 多線程[1037:95068] 同步函數(shù)之后 <NSThread: 0x7f8fd2601920>{number = 1, name = main}

  • 總結(jié) : 根據(jù)打印結(jié)果可以看出,它是按順序執(zhí)行的,當(dāng)同步函數(shù)將任務(wù)添加到隊列中后,不會立即返回,而是等待任務(wù)執(zhí)行完畢之后才返回的,而且全部都是在主線程中執(zhí)行的任務(wù).

  • 先在main queue中執(zhí)行第一個NSLog

  • dispatch_sync會將block提交到queue中账嚎,等待block的執(zhí)行

  • queue中block前面的任務(wù)執(zhí)行完成之后,block執(zhí)行

  • block執(zhí)行完成之后儡蔓,dispatch_sync返回

  • dispatch_sync之后的代碼執(zhí)行

線程死鎖問題

  • 造成死鎖的原因 : 由于dispatch_sync(同步)需要等待block被執(zhí)行郭蕉,這就非常容易發(fā)生死鎖。如果一個串行隊列喂江,使用dispatch_sync提交block到自己隊列中召锈,就會發(fā)生死鎖
死鎖示例
- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"同步函數(shù)之前 %@",[NSThread currentThread]);
    /**
     * 參數(shù) 1: 隊列的名稱  參數(shù) 2: 隊列的類型
     * DISPATCH_QUEUE_SERIAL NULL  // 表示串行隊列
     * DISPATCH_QUEUE_CONCURRENT  // 表示并發(fā)隊列
     */
    dispatch_queue_t queue = dispatch_queue_create("我的微博是@WilliamAlex大叔", DISPATCH_QUEUE_SERIAL);

        dispatch_async(queue, ^{
            NSLog(@"異步函數(shù)中%@",[NSThread currentThread]);
            dispatch_sync(queue, ^{

                NSLog(@"同步函數(shù)中%@",[NSThread currentThread]);
            });
        });

        NSLog(@"同步函數(shù)之后 %@",[NSThread currentThread]);
}

打印結(jié)果
2016-03-21 22:40:33.280 01 - 多線程[1112:100793] 同步函數(shù)之前 <NSThread: 0x7ff17a707510>{number = 1, name = main}
2016-03-21 22:40:33.281 01 - 多線程[1112:100793] 同步函數(shù)之后 <NSThread: 0x7ff17a707510>{number = 1, name = main}
2016-03-21 22:40:33.281 01 - 多線程[1112:100849] 異步函數(shù)中<NSThread: 0x7ff17a501380>{number = 2, name = (null)}

  • 總結(jié) :
    • 上面的代碼已經(jīng)造成了死鎖現(xiàn)象,原因是同步(dispatch_sync)將任務(wù)添加到隊列中后,仍然需要等待任務(wù)執(zhí)行完畢,前面創(chuàng)建隊列queue時,它的類型是串行隊列,串行隊列的本質(zhì)是一個接一個的執(zhí)行,所以block執(zhí)行任務(wù)也需要等待前面的任務(wù)執(zhí)行完畢,也就是等待dispatch_sync執(zhí)行完畢,兩者相互謙讓,相互等待,導(dǎo)致兩個都沒有執(zhí)行,所以執(zhí)行永遠(yuǎn)沒有執(zhí)行完畢,這就是造成死鎖的本質(zhì)原因.
  • 根據(jù)上面打印的結(jié)果可以得出造成死鎖的條件:
    • 創(chuàng)建的隊列是串行隊列
    • 使用同步(dispatch_sync)將任務(wù)添加到自己的隊列中
  • 注意 : 如果queue是并行隊列,或者將任務(wù)加入到其他隊列中开呐,就不會發(fā)生死鎖的現(xiàn)象了烟勋。
寫到這里我們需要總結(jié)一下,否則以后我們只會越來越害怕多線程,害怕去接觸網(wǎng)絡(luò)相關(guān)的知識,做事情不能模棱兩可,既然做了,就要弄明白.
  • 上面我們學(xué)習(xí)了多線程執(zhí)行任務(wù)的方法(即同步和異步)和隊列的運行方式(串行和并行)這里會很繞,需要弄清楚他們之間的本質(zhì),才能更好的運用它們.
  • 下面主要是通過它們之間的區(qū)別以及其自身本質(zhì)來區(qū)分他們
  • 同步和異步的主要區(qū)別: 是否具備開啟子線程的能力
    • 同步 : 只能在當(dāng)前線程上執(zhí)行任務(wù),不具備開啟新線程的能力
    • 異步 : 可以在新線程上執(zhí)行任務(wù),具備開啟新線程的能力
  • 串行和并發(fā)的主要區(qū)別: 任務(wù)的執(zhí)行方式
    • 串行 : 只要當(dāng)當(dāng)前任務(wù)執(zhí)行完畢之后,才能執(zhí)行下一個任務(wù),是有序的執(zhí)行任務(wù)
    • 并發(fā) : 允許多個任務(wù)(并發(fā))同時執(zhí)行
      同步:異步:串行:并發(fā).png

下面我們具體舉例

  • 同步函數(shù) + 主隊列(死鎖啦)
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"-------------begin");
    // 1, 獲取主隊列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
//    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

    // 將任務(wù)添加到隊列中
    dispatch_sync(mainQueue, ^{

        NSLog(@"1我的微博@WilliamAlex大叔%@",[NSThread currentThread]);

    });

    NSLog(@"-------------End");
}
}

打印結(jié)果

2016-03-22 10:39:17.524 01 - 多線程[655:22430] -------------begin

  • 注意 :示例中的全局隊列g(shù)lobalQueue是用來檢測線程是不是死鎖了.

異步函數(shù) + 主隊列

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"-------------begin");
    // 1, 獲取主隊列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

    // 將任務(wù)添加到隊列中
    dispatch_async(mainQueue, ^{

        NSLog(@"哎呀!媽呀, 多線程的水很深啊%@",[NSThread currentThread]);
    });

    NSLog(@"-------------End");
}

打印結(jié)果

2016-03-22 10:44:01.294 01 - 多線程[668:23853] -------------begin
2016-03-22 10:44:01.294 01 - 多線程[668:23853] -------------End
2016-03-22 10:44:01.302 01 - 多線程[668:23853] 哎呀!媽呀, 多線程的水很深啊<NSThread: 0x7fe583700bd0>{number = 1, name = main}

  • 注意 : 打印結(jié)果表示 : 當(dāng)使用異步函數(shù)時,當(dāng)將block中的任務(wù)添加到隊列中后,就會立即返回,不會等待任務(wù)的執(zhí)行.

同步函數(shù) + 串行隊列

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"-------------begin");
    // 1, 獲取串行:DISPATCH_QUEUE_SERIAL 或 NULL即可
    dispatch_queue_t syncSerial = dispatch_queue_create("WilliamAlex大叔", DISPATCH_QUEUE_SERIAL);

    // 將任務(wù)添加到隊列中
    dispatch_sync(syncSerial, ^{

        NSLog(@"1WilliiamAlex大叔%@",[NSThread currentThread]);
    });

    dispatch_sync(syncSerial, ^{

        NSLog(@"2WilliiamAlex大叔%@",[NSThread currentThread]);
    });

    dispatch_sync(syncSerial, ^{

        NSLog(@"3WilliiamAlex大叔%@",[NSThread currentThread]);
    });
    NSLog(@"-------------End");
}

打印結(jié)果

2016-03-22 10:52:32.478 01 - 多線程[703:27166] -------------begin
2016-03-22 10:52:32.478 01 - 多線程[703:27166] 1WilliiamAlex大叔<NSThread: 0x7fbb0b6075d0>{number = 1, name = main}
2016-03-22 10:52:32.479 01 - 多線程[703:27166] 2WilliiamAlex大叔<NSThread: 0x7fbb0b6075d0>{number = 1, name = main}
2016-03-22 10:52:32.479 01 - 多線程[703:27166] 3WilliiamAlex大叔<NSThread: 0x7fbb0b6075d0>{number = 1, name = main}
2016-03-22 10:52:32.479 01 - 多線程[703:27166] -------------End
  • 注意: 從打印結(jié)果中看,同步+串行:當(dāng)將block中的任務(wù)添加到隊列中后,并不會立即返回,而是等待任務(wù)執(zhí)行完畢,并且任務(wù)是一個一個執(zhí)行的,是有序的,不會開啟新的線程.

異步函數(shù) + 串行隊列

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"-------------begin");
    // 1, 獲取串行:DISPATCH_QUEUE_SERIAL 或 NULL即可
    dispatch_queue_t syncSerial = dispatch_queue_create("WilliamAlex大叔", DISPATCH_QUEUE_SERIAL);

    // 將任務(wù)添加到隊列中
    dispatch_async(syncSerial, ^{

        NSLog(@"1WilliiamAlex大叔%@",[NSThread currentThread]);
    });

    dispatch_async(syncSerial, ^{

        NSLog(@"2WilliiamAlex大叔%@",[NSThread currentThread]);
    });

    dispatch_async(syncSerial, ^{

        NSLog(@"3WilliiamAlex大叔%@",[NSThread currentThread]);
    });

    NSLog(@"-------------End");
}

打印結(jié)果

2016-03-22 10:57:45.433 01 - 多線程[716:28932] -------------begin
2016-03-22 10:57:45.434 01 - 多線程[716:28932] -------------End
2016-03-22 10:57:45.434 01 - 多線程[716:28983] 1WilliiamAlex大叔<NSThread: 0x7f7ff0c20060>{number = 2, name = (null)}
2016-03-22 10:57:45.434 01 - 多線程[716:28983] 2WilliiamAlex大叔<NSThread: 0x7f7ff0c20060>{number = 2, name = (null)}
2016-03-22 10:57:45.434 01 - 多線程[716:28983] 3WilliiamAlex大叔<NSThread: 0x7f7ff0c20060>{number = 2, name = (null)}

  • 注意 : 異步 + 串行 : 是有序執(zhí)行的,開啟了新的線程,注意,只要是異步函數(shù),當(dāng)將block中的惹怒添加到隊列中后會立即返回,不會等待任務(wù)的執(zhí)行.

同步函數(shù) + 并發(fā)隊列

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"-------------begin");
    // 1, 獲取并發(fā)隊列 : DISPATCH_QUEUE_CONCURRENT
//    dispatch_queue_t syncSerial = dispatch_queue_create("WilliamAlex大叔", DISPATCH_QUEUE_CONCURRENT);

    // 可以直接使用全局的并發(fā)隊列(第一個0:表示系統(tǒng)默認(rèn)即可. 第二個0:表示預(yù)留字段)
    dispatch_queue_t globalQuque = dispatch_get_global_queue(0, 0);

    // 將任務(wù)添加到隊列中

    dispatch_sync(globalQuque, ^{

        for (NSInteger i = 0; i < 3; i++) {

            NSLog(@"1WilliiamAlex大叔%@",[NSThread currentThread]);
        }
    });

    dispatch_sync(globalQuque, ^{

        for (NSInteger i = 0; i < 3; i++) {

            NSLog(@"2WilliiamAlex大叔%@",[NSThread currentThread]);
        }
    });

    NSLog(@"-------------End");
}

打印結(jié)果

2016-03-22 11:13:23.988 01 - 多線程[794:35399] -------------begin
2016-03-22 11:13:23.989 01 - 多線程[794:35399] 1WilliiamAlex大叔<NSThread: 0x7fafea506970>{number = 1, name = main}
2016-03-22 11:13:23.989 01 - 多線程[794:35399] 1WilliiamAlex大叔<NSThread: 0x7fafea506970>{number = 1, name = main}
2016-03-22 11:13:23.990 01 - 多線程[794:35399] 1WilliiamAlex大叔<NSThread: 0x7fafea506970>{number = 1, name = main}
2016-03-22 11:13:23.990 01 - 多線程[794:35399] 2WilliiamAlex大叔<NSThread: 0x7fafea506970>{number = 1, name = main}
2016-03-22 11:13:23.990 01 - 多線程[794:35399] 2WilliiamAlex大叔<NSThread: 0x7fafea506970>{number = 1, name = main}
2016-03-22 11:13:23.990 01 - 多線程[794:35399] 2WilliiamAlex大叔<NSThread: 0x7fafea506970>{number = 1, name = main}
2016-03-22 11:13:23.990 01 - 多線程[794:35399] -------------End
  • 注意 : 同步 + 并發(fā):都是在主線程上執(zhí)行的,并且執(zhí)行方式是只有當(dāng)前面的所有任務(wù)有序低執(zhí)行完畢之后,才會執(zhí)行下一個任務(wù),不具備開啟新線程的能力.

異步函數(shù) + 并發(fā)隊列

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"-------------begin");
    // 1, 獲取并發(fā)隊列 : DISPATCH_QUEUE_CONCURRENT
//    dispatch_queue_t syncSerial = dispatch_queue_create("WilliamAlex大叔", DISPATCH_QUEUE_CONCURRENT);

    // 可以直接使用全局的并發(fā)隊列(第一個0:表示系統(tǒng)默認(rèn)即可. 第二個0:表示預(yù)留字段)
    dispatch_queue_t globalQuque = dispatch_get_global_queue(0, 0);

    // 將任務(wù)添加到隊列中

    dispatch_async(globalQuque, ^{

        for (NSInteger i = 0; i < 10; i++) {

            NSLog(@"1WilliiamAlex大叔%@",[NSThread currentThread]);
        }
    });

    dispatch_async(globalQuque, ^{

        for (NSInteger i = 0; i < 10; i++) {

            NSLog(@"2WilliiamAlex大叔%@",[NSThread currentThread]);
        }
    });

    NSLog(@"-------------End");
}

打印結(jié)果(部分打印)

2016-03-22 11:09:43.346 01 - 多線程[774:33864] -------------begin
2016-03-22 11:09:43.347 01 - 多線程[774:33864] -------------End
2016-03-22 11:09:43.347 01 - 多線程[774:33908] 2WilliiamAlex大叔<NSThread: 0x7fa45b43d030>{number = 2, name = (null)}
2016-03-22 11:09:43.347 01 - 多線程[774:33907] 1WilliiamAlex大叔<NSThread: 0x7fa45b61fcf0>{number = 3, name = (null)}
2016-03-22 11:09:43.348 01 - 多線程[774:33908] 2WilliiamAlex大叔<NSThread: 0x7fa45b43d030>{number = 2, name = (null)}
2016-03-22 11:09:43.348 01 - 多線程[774:33907] 1WilliiamAlex大叔<NSThread: 0x7fa45b61fcf0>{number = 3, name = (null)}
2016-03-22 11:09:43.348 01 - 多線程[774:33908] 2WilliiamAlex大叔<NSThread:
  • 注意 : 異步 + 并發(fā):開啟了子線程,執(zhí)行順序是亂序的,并且將任務(wù)添加到隊列中后就立即返回了,不用等待任務(wù)的執(zhí)行.
  • 補(bǔ)充
  • GCD中還提供了一個函數(shù)來執(zhí)行任務(wù),但是它有點特殊.
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

  • 該函數(shù)的使用場景 : 有時候我們需要讓某個任務(wù)單獨執(zhí)行,也就是說在執(zhí)行的時候,不允許其他的任務(wù)執(zhí)行.這時候我們就可以使用dispatch_barrier.
dispatch_barrier示例
- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"同步函數(shù)之前 %@",[NSThread currentThread]);
    /**
     * 參數(shù) 1: 隊列的名稱  參數(shù) 2: 隊列的類型
     * DISPATCH_QUEUE_SERIAL NULL  // 表示串行隊列
     * DISPATCH_QUEUE_CONCURRENT  // 表示并發(fā)隊列
     */
    dispatch_queue_t queue = dispatch_queue_create("我的微博是@WilliamAlex大叔", DISPATCH_QUEUE_CONCURRENT);

        dispatch_sync(queue, ^{

         NSLog(@"同步函數(shù)中%@",[NSThread currentThread]);
        });

    dispatch_barrier_async(queue, ^{

        NSLog(@"猜猜我在哪里執(zhí)行%@",[NSThread currentThread]);
    });

        NSLog(@"同步函數(shù)之后 %@",[NSThread currentThread]);

}

打印結(jié)果
2016-03-21 23:44:55.517 01 - 多線程[1196:116016] 同步函數(shù)之前 <NSThread: 0x7ffea8c02a00>{number = 1, name = main}
2016-03-21 23:44:55.517 01 - 多線程[1196:116016] 同步函數(shù)中<NSThread: 0x7ffea8c02a00>{number = 1, name = main}
2016-03-21 23:44:55.517 01 - 多線程[1196:116016] 同步函數(shù)之后 <NSThread: 0x7ffea8c02a00>{number = 1, name = main}
2016-03-21 23:44:55.518 01 - 多線程[1196:116084] 猜猜我在哪里執(zhí)行<NSThread: 0x7ffea8e5ba80>{number = 2, name = (null)}

  • 從打印結(jié)果中獲取到的信息 : 當(dāng)我們使用dispatch_barrier將任務(wù)添加到隊列中,隊列中的任務(wù)會在前面所有的任務(wù)執(zhí)行完畢后執(zhí)行,當(dāng)dispatch_barrier執(zhí)行任務(wù)過程中,其它任務(wù)是不允許執(zhí)行的,直到barrier任務(wù)執(zhí)行完成
  • 還有一個有趣的信息,從打印出來的結(jié)果中可以看到dispatch_barrier上執(zhí)行的任務(wù)都是在子線程上執(zhí)行的.所以,dispatch_barrier是將任務(wù)添加到并發(fā)隊列中的.
知識拓展
  • dispatch_barrier最典型的使用場景是讀寫問題,NSMutableDictionary在多個線程中如果同時寫入筐付,或者一個線程寫入一個線程讀取卵惦,會發(fā)生無法預(yù)料的錯誤。但是他可以在多個線程中同時讀取瓦戚。如果多個線程同時使用同一個NSMutableDictionary沮尿。怎樣才能保護(hù)NSMutableDictionary不發(fā)生意外呢?
- (void)setObject:(id)anObject forKey:(id
)aKey
{
    dispatch_barrier_async(self.concurrentQueue, ^{
        [self.mutableDictionary setObject:anObject forKey:aKey];
    });
}

- (id)objectForKey:(id)aKey
{
    __block id object = nil;
    dispatch_sync(self.concurrentQueue, ^{
        object = [self.mutableDictionary objectForKey:aKey];
    });    return  object;
}
  • 當(dāng)NSMutableDictionary寫入的時候较解,我們使用dispatch_barrier_async畜疾,讓其單獨執(zhí)行寫入操作,不允許其他寫入操作或者讀取操作同時執(zhí)行印衔。當(dāng)讀取的時候啡捶,我們只需要直接使用dispatch_sync,讓其正常讀取即可奸焙。這樣就可以保證寫入時不被打擾瞎暑,讀取時可以多個線程同時進(jìn)行

  • 總結(jié) : 使用dispatch_barrier的前提是,隊列必須是并發(fā)隊列,但是這個queue(隊列)不能是全局隊列.

  • dispatch_barrier的最根本的原理 : 只有它前面所有的任務(wù)都執(zhí)行完畢之后才會執(zhí)行dispatch_barrier中隊列的任務(wù),并且在執(zhí)行任務(wù)過程中,其它任務(wù)是不允許執(zhí)行的,也就是說,當(dāng)它在執(zhí)行過程中,只有它在執(zhí)行.

創(chuàng)建隊列

  • 創(chuàng)建隊列 : 使用dispatch_queue_create函數(shù)創(chuàng)建的隊列.
/*
    參數(shù) 1: 隊列的名稱
    參數(shù) 2: 隊列的類型
    * NULL                         // 表示串行隊列
    * DISPATCH_QUEUE_SERIAL       // 表示串行隊列
    * DISPATCH_QUEUE_CONCURRENT  // 表示并發(fā)隊列
*/
    dispatch_queue_t queue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
  • 串行隊列

使用dispatch_queue_create函數(shù)創(chuàng)建串行隊列
// 創(chuàng)建串行隊列(隊列類型傳遞NULL或者DISPATCH_QUEUE_SERIAL)

// 方式 1 : NULL
 dispatch_queue_t queue = dispatch_queue_create(@"com.William.Alex", NULL);

// 方式 2 : DISPATCH_QUEUE_SERIAL
 dispatch_queue_t queue = dispatch_queue_create(@"com.William.Alex", DISPATCH_QUEUE_SERIAL);

使用主隊列(跟主線程相關(guān)聯(lián)的隊列)
主隊列是GCD自帶的一種特殊的串行隊列
放在主隊列中的任務(wù),都會放到主線程中執(zhí)行
使用dispatch_get_main_queue()獲得主隊列
dispatch_queue_t queue = dispatch_get_main_queue();

  • 并發(fā)隊列
 dispatch_queue_t queue = dispatch_queue_create(@"com.William.Alex", DISPATCH_QUEUE_CONCURRENT);
  • 全局并發(fā)隊列
GCD默認(rèn)已經(jīng)提供了全局的并發(fā)隊列与帆,供整個應(yīng)用使用了赌,可以無需手動創(chuàng)建
使用dispatch_get_global_queue函數(shù)獲得全局的并發(fā)隊列
dispatch_queue_t dispatch_get_global_queue(
dispatch_queue_priority_t priority, // 隊列的優(yōu)先級
unsigned long flags); // 用0即可

獲得全局并發(fā)隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

全局并發(fā)隊列的優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(rèn)(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺

知識拓展

  • 全局并發(fā)隊列函數(shù)中的兩個參數(shù):
  • identifier: 用以標(biāo)識隊列優(yōu)先級,推薦用qos_class枚舉作為參數(shù)玄糟,也可以使用dispatch_queue_priority_t
  • flags: 預(yù)留字段勿她,傳入任何非0的值都可能導(dǎo)致返回NULL.
  • 獲取主隊列
// 直接獲取主隊列
NSLog(@"主隊列上的任務(wù)都是在主線程上執(zhí)行的%@",dispatch_get_main_queue());
主隊列的使用場景
  • 主線程是我們最常用的線程,GCD提供了非常簡單的獲取主線程隊列的方法.我們使用主隊列或者是主線程主要是執(zhí)行一些UI界面的操作(比如:UI界面的屬性等事件),記住:只要是一些耗時的操作一般都是放在子線程上執(zhí)行,不耗時的操作就放在主線程上執(zhí)行.
dispatch_async(dispatch_get_main_queue(), ^{
    // 刷新UI界面操作
});
  • 執(zhí)行加入到主線程隊列的block阵翎,App會調(diào)用dispatch_main(), NSApplicationMain()逢并,或者在主線程使用CFRunLoop。

  • 有的朋友習(xí)慣將參數(shù)都設(shè)置為0

很多時候我們喜歡將0或者NULL傳入作為參數(shù)

dispatch_get_global_queue(NULL, NULL)

由于NULL等于0郭卫,也就是DISPATCH_QUEUE_PRIORITY_DEFAULT筒狠,所以返回的是默認(rèn)優(yōu)先級

網(wǎng)絡(luò)延遲

dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *context, dispatch_function_t work);
  • 幾種延遲操作
    • 定時器(NSTimer)
    • GCD(dispatch_after)
    • 調(diào)用NSObject方法(performeSelector)
  • NSTimer定時器
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(run) userInfo:nil repeats:YES];

  • performeSelector
// 直接調(diào)用NSObject方法,,performeSelector方法也是比較常見具體實現(xiàn)是.
// nonnull : 表示參數(shù)不能設(shè)置為空
// nullable : 表示參數(shù)可以設(shè)置為空
[self performSelector:(nonnull SEL) withObject:(nullable id) afterDelay:(NSTimeInterval)]

[self performSelector:(nonnull SEL) withObject:(nullable id) afterDelay:(NSTimeInterval) inModes:(nonnull NSArray<NSString *> *)]
  • GCD(dispatch_after)
/*
#define DISPATCH_TIME_NOW (0ull)
#define DISPATCH_TIME_FOREVER (~0ull)
注意 : 不能傳DISPATCH_TIME_FOREVER,會一直阻塞線程
*/
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, ^(void)block)

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //  2.0
    });

總結(jié)延遲操作

  • 函數(shù)說明: 2.0秒之后block中的任務(wù)會加到隊列中,兩個函數(shù)的第一個參數(shù)都是when,表示時間,當(dāng)我們傳入DISPATCH_TIME_NOW當(dāng)前函數(shù)就相當(dāng)于是一個dispatch_async(異步函數(shù)).值得注意的是,我們不能傳入DISPATCH_TIME_FOREVER,因為這樣會造成線程的阻塞.

  • 參數(shù)說明:dispatch_after接收block作為參數(shù),系統(tǒng)持有block箱沦,block中self不需要weak辩恼。dispatch_after_f接收work函數(shù)作為參數(shù),context作為work函數(shù)的第一個參數(shù)

  • 注意: 需要注意的是這里的延時并不精確的,因為加入隊列不一定會立即執(zhí)行旺订。延時1s可能會1.5s甚至2s之后才會執(zhí)行向抢。

dispatch_queue_set_specific 和 dispatch_queue_get_specific

dispatch_queue_set_specific的使用場景

  • 當(dāng)我們需要將某些東西關(guān)聯(lián)在隊列上,比如說我們想在隊列上存儲一些東西,又或者我們想?yún)^(qū)分兩個隊列。GCD提供了dispatch_queue_set_specific方法聘萨,通過key,將context關(guān)聯(lián)到queue上
void dispatch_queue_set_specific(dispatch_queue_t queue, const void *key, void *context, dispatch_function_t destructor);

參數(shù)說明

  • queue:需要關(guān)聯(lián)的queue童太,不允許傳入nil
  • key:隊列的標(biāo)識
  • context:要關(guān)聯(lián)的內(nèi)容米辐,可以為nil
  • destructor:釋放context的函數(shù)胸完,當(dāng)新的context被設(shè)置時,destructor會被調(diào)用

dispatch_queue_get_specific的使用場景

  • 有存就有取翘贮,將context關(guān)聯(lián)到queue上之后赊窥,可以通過dispatch_queue_get_specific或者dispatch_get_specific方法將值取出來。
void *dispatch_queue_get_specific(dispatch_queue_t queue, const void *key);
void *dispatch_get_specific(const void *key);

dispatch_queue_get_specific的解釋

  • 根據(jù)queue和key取出context狸页,queue參數(shù)不能傳入全局隊列

  • dispatch_get_specific: 根據(jù)唯一的key取出當(dāng)前queue的context锨能。如果當(dāng)前queue沒有key對應(yīng)的context,則去queue的target queue取芍耘,取不著返回nil.

  • 如果對全局隊列取址遇,也會返回nil
    iOS 6之后dispatch_get_current_queue()被廢棄,如果我們需要區(qū)分不同的queue斋竞,可以使用set_specific方法倔约。根據(jù)對應(yīng)的key是否有值來區(qū)分

一個人的力量是有限的,如果文章有有錯誤,希望大家指出來,相互幫助,相互進(jìn)步.感謝@小笨狼Lc提供資料,想要了解更多知識可以點進(jìn)連接,關(guān)注CocoaChina的微信公眾號.http://www.cocoachina.com/ios/20160225/15422.html
后續(xù)會持續(xù)更新.....加油!!!!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市坝初,隨后出現(xiàn)的幾起案子跺株,更是在濱河造成了極大的恐慌,老刑警劉巖脖卖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乒省,死亡現(xiàn)場離奇詭異,居然都是意外死亡畦木,警方通過查閱死者的電腦和手機(jī)袖扛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來十籍,“玉大人蛆封,你說我怎么就攤上這事」蠢酰” “怎么了惨篱?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長围俘。 經(jīng)常有香客問我砸讳,道長,這世上最難降的妖魔是什么界牡? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任簿寂,我火速辦了婚禮,結(jié)果婚禮上宿亡,老公的妹妹穿的比我還像新娘常遂。我一直安慰自己,他們只是感情好挽荠,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布克胳。 她就那樣靜靜地躺著平绩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪漠另。 梳的紋絲不亂的頭發(fā)上捏雌,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機(jī)與錄音酗钞,去河邊找鬼腹忽。 笑死来累,一個胖子當(dāng)著我的面吹牛砚作,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嘹锁,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼葫录,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了领猾?” 一聲冷哼從身側(cè)響起米同,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎摔竿,沒想到半個月后面粮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡继低,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年熬苍,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袁翁。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡柴底,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粱胜,到底是詐尸還是另有隱情柄驻,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布焙压,位于F島的核電站鸿脓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏涯曲。R本人自食惡果不足惜答憔,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望掀抹。 院中可真熱鬧虐拓,春花似錦、人聲如沸傲武。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至态兴,卻和暖如春狠持,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瞻润。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工喘垂, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绍撞。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓正勒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親傻铣。 傳聞我的和親對象是個殘疾皇子章贞,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

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