iOS多線程之GCD學習筆記

前言

GCD是iOS開發(fā)中十分重要的多線程方案之一,通過對大神文章的學習甩苛,此篇文章為代碼的練習和一部分自己學習的總結和記錄会宪。

原文章鏈接:http://www.reibang.com/p/2d57c72016c6

簡介

GCD的全稱是Grand Central Dispatch,可譯為“牛逼的中樞調(diào)度器”元践,純C語言韭脊,提供了非常多強大的函數(shù)。

  • GCD是蘋果公司為多核的并行運算提出的解決方案
  • GCD會自動利用更多的CPU內(nèi)核(比如雙核单旁、四核)
  • GCD會自動管理線程的生命周期(創(chuàng)建線程沪羔、調(diào)度任務、銷毀線程)

GCD任務和隊列

GCD中兩個核心的概念:任務和隊列
任務:就是要執(zhí)行的操作象浑,在GCD中執(zhí)行操作的代碼塊是寫在block中的蔫饰。任務有兩種派發(fā)方式:同步派發(fā)和異步派發(fā)琅豆。

同步派發(fā)和異步派發(fā)的區(qū)別就是是否會阻塞當前線程,同步派發(fā)的任務在當前線程執(zhí)行篓吁,并使當前線程等待派發(fā)的任務執(zhí)行完成才能繼續(xù)執(zhí)行茫因。異步派發(fā)的任務在其他線程執(zhí)行,不會阻塞當前線程杖剪。冻押。當然還有一條優(yōu)先級更高的規(guī)則,凡是派發(fā)到主隊列的任務都會在主線程執(zhí)行摘盆。例如翼雀,在子線程同步派發(fā)任務到主線程,則不會在當前線程執(zhí)行孩擂±窃ǎ或者在主線程異步派發(fā)到主線程,也不會在其他線程執(zhí)行类垦。當以某種派發(fā)方式向某種隊列多次派發(fā)任務后狈邑,執(zhí)行結果如下表所示。

隊列:隊列是一種特殊的線性表蚤认,采用 FIFO(先進先出)的原則米苹,GCD中有兩種隊列,串行隊列和并發(fā)隊列砰琢。兩者都符合 FIFO(先進先出)的原則蘸嘶。兩者的主要區(qū)別是:執(zhí)行順序不同,以及開啟線程數(shù)不同陪汽。

串行隊列:每次只有一個任務被執(zhí)行训唱。讓任務一個接著一個地執(zhí)行。(只開啟一個線程挚冤,一個任務執(zhí)行完畢后况增,再執(zhí)行下一個任務)。

并發(fā)隊列:可以讓多個任務并發(fā)(同時)執(zhí)行训挡。(可以開啟多個線程澳骤,并且同時執(zhí)行任務)。

總結 同步派發(fā) 異步派發(fā)
串行隊列 當前線程串行執(zhí)行澜薄,阻塞當前線程 新建單個線程串行執(zhí)行为肮,不阻塞當前線程。
并發(fā)隊列 當前線程串行執(zhí)行肤京,阻塞當前線程 新建多個線程并發(fā)執(zhí)行颊艳,不阻塞當前線程。
主隊列 主線程串行執(zhí)行,阻塞當前線程 主線程串行執(zhí)行籽暇,不阻塞當前線程

異步執(zhí)行(async)雖然具有開啟新線程的能力温治,但是并不一定開啟新線程。
并發(fā)隊列只有在異步執(zhí)行的情況下才會出現(xiàn)并發(fā)戒悠。

GCD的使用步驟

  1. 創(chuàng)建一個隊列(串行隊列或并發(fā)隊列)
  2. 將任務追加到任務的等待隊列中熬荆,然后系統(tǒng)就會根據(jù)任務類型執(zhí)行任務(同步執(zhí)行或異步執(zhí)行)

隊列的創(chuàng)建和獲取

    //串行隊列創(chuàng)建
    dispatch_queue_t queue1 = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    //并發(fā)隊列的創(chuàng)建
    dispatch_queue_t queue2 = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    //主隊列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    //全局并發(fā)隊列 第一個參數(shù)表示隊列的優(yōu)先級,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT绸狐,第二個參數(shù)s用0即可
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

任務的創(chuàng)建方法

GCD提供了同步執(zhí)行任務和異步執(zhí)行任務

    //同步執(zhí)行任務的創(chuàng)建方法
    dispatch_sync(queue, ^{
        //這里放同步執(zhí)行任務代碼
    })
    //異步執(zhí)行任務的創(chuàng)建方法
    dispatch_async(queue, ^{
        //這里放異步執(zhí)行任務代碼
    });

GCD的使用組合

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

同步執(zhí)行的原則是不會開辟新的線程卤恳,就在當前線程執(zhí)行,同步執(zhí)行并發(fā)隊列的情況寒矿,不會出現(xiàn)并發(fā)突琳,因為同步執(zhí)行不能開辟新的線程,只有當前一個線程符相,所以不存在并發(fā)拆融,而且當前線程只有等待當前隊列正在執(zhí)行的任務執(zhí)行完畢后,在能繼續(xù)執(zhí)行下面的操作啊终,依然是執(zhí)行完一個任務后镜豹,再執(zhí)行下一個任務。

所有任務都在打印的syncConcurrent---begin和syncConcurrent---end之間執(zhí)行的蓝牲,同步任務需要等待隊列的任務執(zhí)行結束趟脂。

    //同步執(zhí)行并發(fā)隊列
    NSLog(@"syncConcurrent---begin");
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    NSLog(@"syncConcurrent---end");

syncConcurrent---begin
1---<NSThread: 0x600002bd9380>{number = 1, name = main}
1---<NSThread: 0x600002bd9380>{number = 1, name = main}
2---<NSThread: 0x600002bd9380>{number = 1, name = main}
2---<NSThread: 0x600002bd9380>{number = 1, name = main}
3---<NSThread: 0x600002bd9380>{number = 1, name = main}
3---<NSThread: 0x600002bd9380>{number = 1, name = main}
syncConcurrent---end

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

異步執(zhí)行會開辟新的線程,異步執(zhí)行下并發(fā)隊列會具備并發(fā)性例衍,會開辟多個線程昔期,同時執(zhí)行多個任務。所以任務都end后才執(zhí)行佛玄,說明任務的執(zhí)行不需要做等待硼一。

    NSLog(@"asyncConcurrent---begin");
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    NSLog(@"asyncConcurrent---end");
    
    
    2019-08-06 18:39:33.823042+0800 MasonryTest[12796:1249138] asyncConcurrent---begin
    asyncConcurrent---end
    1---<NSThread: 0x600000ec0340>{number = 4, name = (null)}
    2---<NSThread: 0x600000ec5040>{number = 3, name = (null)}
    2---<NSThread: 0x600000ed81c0>{number = 5, name = (null)}
    1---<NSThread: 0x600000ec0340>{number = 4, name = (null)}
    2---<NSThread: 0x600000ec5040>{number = 3, name = (null)}
    2---<NSThread: 0x600000ed81c0>{number = 5, name = (null)}

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

同步執(zhí)行,并不會開辟新的線程翎嫡,就在當前線程執(zhí)行欠动,串行隊列的特點就是執(zhí)行完一個任務之后永乌,再執(zhí)行下一個任務惑申。

所有任務都在打印的syncConcurrent---begin和syncConcurrent---end之間執(zhí)行(同步任務需要等待隊列的任務執(zhí)行結束)。

//同步執(zhí)行 串行隊列
    NSLog(@"asyncConcurrent---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    NSLog(@"asyncConcurrent---end");
    
    asyncConcurrent---begin
    1---<NSThread: 0x600000ae5340>{number = 1, name = main}
    1---<NSThread: 0x600000ae5340>{number = 1, name = main}
    2---<NSThread: 0x600000ae5340>{number = 1, name = main}
    2---<NSThread: 0x600000ae5340>{number = 1, name = main}
    3---<NSThread: 0x600000ae5340>{number = 1, name = main}
    3---<NSThread: 0x600000ae5340>{number = 1, name = main}
    asyncConcurrent---end

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

異步執(zhí)行會開辟新的線程翅雏,由于是串行隊列圈驼,所以只開辟了一個線程。

所有任務是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執(zhí)行的望几,異步執(zhí)行不做任何等待绩脆,可以繼續(xù)執(zhí)行任務。

任務是按順序執(zhí)行的,串行隊列只有一個任務執(zhí)行完后靴迫,下一個任務才會執(zhí)行惕味。

    NSLog(@"asyncConcurrent---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    NSLog(@"asyncConcurrent---end");
    
    asyncConcurrent---begin
    asyncConcurrent---end
    1---<NSThread: 0x600002910480>{number = 3, name = (null)}
    1---<NSThread: 0x600002910480>{number = 3, name = (null)}
    2---<NSThread: 0x600002910480>{number = 3, name = (null)}
    2---<NSThread: 0x600002910480>{number = 3, name = (null)}
    3---<NSThread: 0x600002910480>{number = 3, name = (null)}
    3---<NSThread: 0x600002910480>{number = 3, name = (null)}

同步執(zhí)行 主隊列

同步執(zhí)行 + 主隊列在不同線程中調(diào)用結果也是不一樣,在主線程中調(diào)用會出現(xiàn)死鎖玉锌,而在其他線程中則不會名挥。

在主線程中調(diào)用同步執(zhí)行 主隊列

形成死鎖。崩潰主守。

這是因為我們在主線程中執(zhí)行syncMain方法禀倔,相當于把syncMain任務放到了主線程的隊列中。而同步執(zhí)行會等待當前隊列中的任務執(zhí)行完畢参淫,才會接著執(zhí)行救湖。那么當我們把任務1追加到主隊列中,任務1就在等待主線程處理完syncMain任務涎才。而syncMain任務需要等待任務1執(zhí)行完畢鞋既,才能接著執(zhí)行。
那么耍铜,現(xiàn)在的情況就是syncMain任務和任務1都在等對方執(zhí)行完畢涛救。這樣大家互相等待,所以就卡住了业扒,所以我們的任務執(zhí)行不了检吆,而且syncMain---end也沒有打印。

任務的追加和任務的執(zhí)行互相等待程储,形成了死鎖蹭沛。當我們將任務1追加到主隊列的時候,任務1就在等待主線程處理完syncMain任務章鲤,而syncMain任務有需要等待任務1執(zhí)行完畢后才能接著執(zhí)行摊灭。

- (void)syncMain{

    //在主線程中 同步執(zhí)行 主隊列
    NSLog(@"asyncConcurrent---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    NSLog(@"asyncConcurrent---end");
}

其他線程中調(diào)用同步執(zhí)行 主隊列

這樣相當于將syncMain任務放到了其他線程中,而任務1败徊,2帚呼,3則追加到了主隊列,在主線程中執(zhí)行皱蹦。因為主隊列現(xiàn)在沒有正在執(zhí)行的任務煤杀,所以會直接執(zhí)行主隊列任務1,然后2沪哺,3依次執(zhí)行沈自,不會形成死鎖。

    [NSThread detachNewThreadWithBlock:^{
        [self syncMain];
    }];
    
    asyncConcurrent---begin
    1---<NSThread: 0x600002299380>{number = 1, name = main}
    1---<NSThread: 0x600002299380>{number = 1, name = main}
    2---<NSThread: 0x600002299380>{number = 1, name = main}
    3---<NSThread: 0x600002299380>{number = 1, name = main}
    3---<NSThread: 0x600002299380>{number = 1, name = main}
    asyncConcurrent---end    
    

當前串行隊列正在執(zhí)行的任務所在的線程”繼續(xù)向當前隊列同步派發(fā)任務辜妓,就會造成死鎖枯途。因為串行隊列是順序執(zhí)行的忌怎,后進入的任務必須等待前邊的任務執(zhí)行完成,而同步派發(fā)的任務則會阻塞當前線程直到自己執(zhí)行完成酪夷。所以榴啸,當前線程如果就是“串行隊列正在執(zhí)行的任務所在的線程”,那么串行隊列正在執(zhí)行的任務就會阻塞晚岭,就無法執(zhí)行后邊同步派發(fā)的任務插掂,同步派發(fā)的任務得不到執(zhí)行,就不會取消當前線程的阻塞狀態(tài)腥例,從而造成了死鎖辅甥。

異步執(zhí)行 主隊列

所有任務都在主線程中執(zhí)行,雖然異步執(zhí)行有能力開辟線程燎竖,但因為是主隊列璃弄,主隊列的任務都在主線程中執(zhí)行。

所有任務是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執(zhí)行的构回。異步執(zhí)行不需要等待夏块,可以繼續(xù)執(zhí)行任務。

主隊列是串行隊列纤掸,因此隊列中添加的任務需要按順序執(zhí)行脐供。

    NSLog(@"asyncConcurrent---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    NSLog(@"asyncConcurrent---end");
    
    asyncConcurrent---begin
    asyncConcurrent---end
    1---<NSThread: 0x6000005ea900>{number = 1, name = main}
    2---<NSThread: 0x6000005ea900>{number = 1, name = main}
    2---<NSThread: 0x6000005ea900>{number = 1, name = main}
    3---<NSThread: 0x6000005ea900>{number = 1, name = main}
    3---<NSThread: 0x6000005ea900>{number = 1, name = main}

總結

同步執(zhí)行不會開辟新的線程,同時會阻塞線程借跪,需要等待追加到隊列中的任務執(zhí)行完畢后才能繼續(xù)追加任務政己。無論是并發(fā)隊列還是串行隊列,隊列中的任務都是順序執(zhí)行的掏愁,需要上一個任務執(zhí)行完后歇由,下一個任務才會執(zhí)行。

異步執(zhí)行具備開辟線程的能力果港,不會阻塞線程沦泌,可以不用等待追加進去的任務執(zhí)行完就可以繼續(xù)向隊列中追加任務,因為異步執(zhí)行不會影響代碼向下繼續(xù)執(zhí)行辛掠。在串行隊列下谢谦,異步執(zhí)行只會開辟一個線程,因為串行隊列的特點是一個任務執(zhí)行完后下一個任務才會執(zhí)行萝衩,因此開辟一個線程就足夠了回挽,串行隊列中的任務按順序依次執(zhí)行。在并發(fā)隊列下欠气,會開辟多個線程厅各,同步執(zhí)行追加進去的任務镜撩。

同步和異步是指派發(fā)任務的方式是否需要等待预柒,同步需要等待队塘,異步不需要等待。串行和并發(fā)是指已經(jīng)在隊列中的任務宜鸯,執(zhí)行時后面的任務的執(zhí)行是否需要等待憔古,串行是按順序執(zhí)行完后再執(zhí)行下一個,并發(fā)則可以不需要等待前面的任務執(zhí)行完時直接進行執(zhí)行淋袖。

主隊列是一個特殊情況鸿市,添加到主隊列中的任務必須要在主線程中執(zhí)行。因此在主線程中調(diào)用同步執(zhí)行主隊列會形成死鎖即碗,主線程需要把任務派發(fā)下去焰情,同步則會阻塞主線程,這樣執(zhí)行任務就需要等待派發(fā)完才能執(zhí)行剥懒,但是派發(fā)任務有需要執(zhí)行任務執(zhí)行完畢后才能派發(fā)内舟,因此派發(fā)任務和執(zhí)行任務出現(xiàn)了互相等待的情況,但是在其他線程中調(diào)用同步執(zhí)行主隊列則不會形成死鎖初橘,此時派發(fā)任務在子線程中進行验游,執(zhí)行任務在主線程中執(zhí)行。異步主隊列仍然是在主線程中執(zhí)行保檐,但是異步不需要等待任務執(zhí)行完在追加新的任務耕蝉,但是任務追加到主隊列依照串行隊列的原則順序執(zhí)行,等待上一個執(zhí)行完后夜只,再執(zhí)行下一個垒在。

GCD線程間的通信

執(zhí)行一些耗時操作的時候,開辟子線程扔亥,執(zhí)行完畢后爪膊,回到主線程更新UI。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (int i = 0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
        
        dispatch_async(dispatch_get_main_queue(), ^{
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        });
    });
    
3---<NSThread: 0x600001c9c9c0>{number = 3, name = (null)}
3---<NSThread: 0x600001c9c9c0>{number = 3, name = (null)}
2---<NSThread: 0x600001cf8ec0>{number = 1, name = main}    

GCD的其他方法砸王。

GCD的柵欄方法:dispatch_barrier_async

我們有時需要異步執(zhí)行兩組操作推盛,而且第一組操作執(zhí)行完之后,才能開始執(zhí)行第二組操作谦铃。這樣我們就需要一個相當于柵欄一樣的一個方法將兩組異步執(zhí)行的操作組給分割起來耘成,當然這里的操作組里可以包含一個或多個任務。這就需要用到dispatch_barrier_async方法在兩個操作組間形成柵欄驹闰。
dispatch_barrier_async函數(shù)會等待前邊追加到并發(fā)隊列中的任務全部執(zhí)行完畢之后瘪菌,再將指定的任務追加到該異步隊列中。然后在dispatch_barrier_async函數(shù)追加的任務執(zhí)行完畢之后嘹朗,異步隊列才恢復為一般動作师妙,接著追加任務到該異步隊列并開始執(zhí)行。

假設有6個任務分為兩組屹培,需求是第一組的三個并發(fā)執(zhí)行默穴,第二組的三個等第一組的三個都執(zhí)行完畢后再并發(fā)執(zhí)行怔檩。

從執(zhí)行結果中可以看出,在執(zhí)行完柵欄前面的操作之后蓄诽,才執(zhí)行柵欄操作薛训,最后再執(zhí)行柵欄后邊的操作。

    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (int i = 0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_barrier_sync(queue, ^{
        for (int i = 0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"barrier---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });

    dispatch_async(queue, ^{
        for (int i = 0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"5---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    2---<NSThread: 0x600001761f00>{number = 3, name = (null)}
    1---<NSThread: 0x60000175b400>{number = 4, name = (null)}
    2---<NSThread: 0x600001761f00>{number = 3, name = (null)}
    1---<NSThread: 0x60000175b400>{number = 4, name = (null)}
    barrier---<NSThread: 0x600001736880>{number = 1, name = main}
    barrier---<NSThread: 0x600001736880>{number = 1, name = main}
    4---<NSThread: 0x60000175b400>{number = 4, name = (null)}
    5---<NSThread: 0x60000175c2c0>{number = 5, name = (null)}
    4---<NSThread: 0x60000175b400>{number = 4, name = (null)}
    5---<NSThread: 0x60000175c2c0>{number = 5, name = (null)}

GCD延時執(zhí)行方法:dispatch_after

在指定時間(例如3秒)之后執(zhí)行某個任務仑氛∫野#可以用 GCD 的dispatch_after函數(shù)來實現(xiàn)。

需要注意的是:dispatch_after函數(shù)并不是在指定時間之后才開始執(zhí)行處理锯岖,而是在指定時間之后將任務追加到主隊列中介袜。嚴格來說,這個時間并不是絕對準確的出吹,但想要大致延遲執(zhí)行任務米酬,dispatch_after函數(shù)是很有效的。

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2.0秒后異步追加任務代碼到主隊列趋箩,并開始執(zhí)行
        NSLog(@"after---%@",[NSThread currentThread]);  // 打印當前線程
    });

GCD一次性代碼:dispatch_once

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只執(zhí)行1次的代碼(這里面默認是線程安全的)
    });

GCD單例

+ (DJSingleton *)shareInstance{
    static DJSingleton * s_instance_dj_singleton = nil ;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (s_instance_dj_manager == nil) {
            s_instance_dj_manager = [[DJSingleton alloc] init];
        }
    });
    return (DJSingleton *)s_instance_dj_singleton;
}

GCD快速迭代方法:dispatch_apply

通常我們會用 for 循環(huán)遍歷赃额,但是 GCD 給我們提供了快速迭代的函數(shù)dispatch_apply。dispatch_apply按照指定的次數(shù)將指定的任務追加到指定的隊列中叫确,并等待全部隊列執(zhí)行結束跳芳。

如果是在串行隊列中使用 dispatch_apply,那么就和 for 循環(huán)一樣竹勉,按順序同步執(zhí)行飞盆。可這樣就體現(xiàn)不出快速迭代的意義了次乓。
我們可以利用并發(fā)隊列進行異步執(zhí)行吓歇。比如說遍歷 0~5 這6個數(shù)字,for 循環(huán)的做法是每次取出一個元素票腰,逐個遍歷城看。dispatch_apply 可以 在多個線程中同時(異步)遍歷多個數(shù)字。
還有一點杏慰,無論是在串行隊列测柠,還是并發(fā)隊列中,dispatch_apply 都會等待全部任務執(zhí)行完畢缘滥,這點就像是同步操作轰胁,也像是隊列組中的 dispatch_group_wait方法。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    NSLog(@"apply---begin");
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
    
    apply---begin
    2---<NSThread: 0x600002f2a880>{number = 1, name = main}
    5---<NSThread: 0x600002f45a80>{number = 6, name = (null)}
    1---<NSThread: 0x600002f502c0>{number = 4, name = (null)}
    3---<NSThread: 0x600002f4a8c0>{number = 5, name = (null)}
    0---<NSThread: 0x600002f23500>{number = 3, name = (null)}
    4---<NSThread: 0x600002f6c140>{number = 7, name = (null)}
    apply---end

GCD 隊列組:dispatch_group

分別異步執(zhí)行2個耗時任務朝扼,然后當2個耗時任務都執(zhí)行完畢后再回到主線程執(zhí)行任務赃阀。這時候我們可以用到 GCD 的隊列組。

  • 調(diào)用隊列組的 dispatch_group_async 先把任務放到隊列中擎颖,然后將隊列放入隊列組中榛斯」塾危或者使用隊列組的 dispatch_group_enter、dispatch_group_leave 組合 來實現(xiàn)dispatch_group_async肖抱。
  • 調(diào)用隊列組的 dispatch_group_notify 回到指定線程執(zhí)行任務备典∫炀桑或者使用 dispatch_group_wait 回到當前線程繼續(xù)向下執(zhí)行(會阻塞當前線程)意述。

dispatch_group_notify

/**
 * 隊列組 dispatch_group_notify
 */
- (void)groupNotify {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"group---begin");
    
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的異步任務1、任務2都執(zhí)行完畢后吮蛹,回到主線程執(zhí)行下邊任務
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
        NSLog(@"group---end");
    });
}

dispatch_group_wait

會阻塞當前線程

/**
 * 隊列組 dispatch_group_wait
 */
- (void)groupWait {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"group---begin");
    
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    // 等待上面的任務全部完成后荤崇,會往下繼續(xù)執(zhí)行(會阻塞當前線程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"group---end");
}

dispatch_group_enter、dispatch_group_leave

/**
 * 隊列組 dispatch_group_enter潮针、dispatch_group_leave
 */
- (void)groupEnterAndLeave
{
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"group---begin");
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的異步操作都執(zhí)行完畢后术荤,回到主線程.
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
        NSLog(@"group---end");
    });
    
}

GCD 信號量:dispatch_semaphore

GCD 中的信號量是指 Dispatch Semaphore,是持有計數(shù)的信號每篷。類似于過高速路收費站的欄桿瓣戚。可以通過時焦读,打開欄桿子库,不可以通過時,關閉欄桿矗晃。在 Dispatch Semaphore 中仑嗅,使用計數(shù)來完成這個功能,計數(shù)小于 0 時等待张症,不可通過仓技。計數(shù)為 0 或大于 0 時,計數(shù)減 1 且不等待俗他,可通過脖捻。
Dispatch Semaphore 提供了三個函數(shù)

  • dispatch_semaphore_create:創(chuàng)建一個 Semaphore 并初始化信號的總量
  • dispatch_semaphore_signal:發(fā)送一個信號,讓信號總量加 1
  • dispatch_semaphore_wait:可以使總信號量減 1兆衅,信號總量小于 0 時就會一直等待(阻塞所在線程)郭变,否則就可以正常執(zhí)行。

Dispatch Semaphore 在實際開發(fā)中主要用于:

  • 保持線程同步涯保,將異步執(zhí)行任務轉(zhuǎn)換為同步執(zhí)行任務
  • 保證線程安全诉濒,為線程加鎖
/**
 * semaphore 線程同步
 */
- (void)semaphoreSync {
    
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"semaphore---begin");
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int number = 0;
    dispatch_async(queue, ^{
        // 追加任務1
        [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        
        number = 100;
        
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %zd",number);
}
  1. semaphore 初始創(chuàng)建時計數(shù)為 0。
  2. 異步執(zhí)行將任務 1 追加到隊列之后夕春,不做等待未荒,接著執(zhí)行dispatch_semaphore_wait方法,semaphore 減 1及志,此時 semaphore == -1片排,當前線程進入等待狀態(tài)寨腔。
  3. 然后,異步任務 1 開始執(zhí)行率寡。任務1執(zhí)行到dispatch_semaphore_signal之后迫卢,總信號量加1,此時 semaphore == 0冶共,正在被阻塞的線程(主線程)恢復繼續(xù)執(zhí)行乾蛤。
  4. 最后打印semaphore---end,number = 100。

這樣就實現(xiàn)了線程同步捅僵,將異步執(zhí)行任務轉(zhuǎn)換為同步執(zhí)行任務

/**
 * 線程安全:使用 semaphore 加鎖
 * 初始化火車票數(shù)量家卖、賣票窗口(線程安全)、并開始賣票
 */
- (void)initTicketStatusSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"semaphore---begin");
    
    semaphoreLock = dispatch_semaphore_create(1);
    
    self.ticketSurplusCount = 50;
    
    // queue1 代表北京火車票售賣窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表上好沓火車票售賣窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf saleTicketSafe];
    });
}

/**
 * 售賣火車票(線程安全)
 */
- (void)saleTicketSafe {
    while (1) {
        // 相當于加鎖
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
        
        if (self.ticketSurplusCount > 0) {  //如果還有票上荡,繼續(xù)售賣
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票數(shù):%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { //如果已賣完,關閉售票窗口
            NSLog(@"所有火車票均已售完");
            
            // 相當于解鎖
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }
        
        // 相當于解鎖
        dispatch_semaphore_signal(semaphoreLock);
    }
}

通過信號量的控制馒闷,保證兩個售票窗口不會同時修改火車票數(shù)量酪捡,從而實現(xiàn)線程安全。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纳账,一起剝皮案震驚了整個濱河市逛薇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌塞祈,老刑警劉巖金刁,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異议薪,居然都是意外死亡尤蛮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門斯议,熙熙樓的掌柜王于貴愁眉苦臉地迎上來产捞,“玉大人,你說我怎么就攤上這事哼御∨髁伲” “怎么了魁亦?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵单寂,是天一觀的道長。 經(jīng)常有香客問我愕秫,道長液肌,這世上最難降的妖魔是什么挟炬? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上谤祖,老公的妹妹穿的比我還像新娘婿滓。我一直安慰自己,他們只是感情好粥喜,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布凸主。 她就那樣靜靜地躺著,像睡著了一般额湘。 火紅的嫁衣襯著肌膚如雪卿吐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天缩挑,我揣著相機與錄音但两,去河邊找鬼鬓梅。 笑死供置,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的绽快。 我是一名探鬼主播芥丧,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼坊罢!你這毒婦竟也來了续担?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤活孩,失蹤者是張志新(化名)和其女友劉穎物遇,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體憾儒,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡询兴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了起趾。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诗舰。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖训裆,靈堂內(nèi)的尸體忽然破棺而出眶根,到底是詐尸還是另有隱情,我是刑警寧澤边琉,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布属百,位于F島的核電站,受9級特大地震影響变姨,放射性物質(zhì)發(fā)生泄漏族扰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望别伏。 院中可真熱鬧蹄衷,春花似錦、人聲如沸厘肮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽类茂。三九已至耍属,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巩检,已是汗流浹背厚骗。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留兢哭,地道東北人领舰。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像迟螺,于是被迫代替她去往敵國和親冲秽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

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