iOS GCD學(xué)習(xí)

看了兩遍 iOS 多線程:『GCD』詳盡總結(jié) ,這里我手打一遍加深下映象,僅供自己加深映象使用,具體參考上面的鏈接

1.GCD簡(jiǎn)介

Grand Central Dispatch(GCD)是Apple開發(fā)的一個(gè)多核編程的較新的解決方法。它主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對(duì)稱多處理系統(tǒng)。它是一個(gè)在線程池模式的基礎(chǔ)上執(zhí)行的并發(fā)任務(wù)。

為什么要用GCD呢(面試也可能問(wèn)用GCD的好處)

  • 可用于多核的并行運(yùn)算
  • 會(huì)自動(dòng)利用更多的CPU內(nèi)核
  • 會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)
  • 程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理的代碼

2.GCD任務(wù)和隊(duì)列

任務(wù): 就是執(zhí)行操作的意思,換句話說(shuō)就是你在線程中執(zhí)行的代碼塊。在GCD中是放在block中的。執(zhí)行任務(wù)有兩種方式:同步執(zhí)行(sync)異步執(zhí)行(async)。兩者的主要區(qū)別是:是否等待隊(duì)列中的任務(wù)執(zhí)行結(jié)束,以及是否具備開啟新線程的能力。

  • 同步執(zhí)行(sync):
    1. 同步添加任務(wù)到指定的任務(wù)隊(duì)列中,在添加的任務(wù)執(zhí)行結(jié)束之前,會(huì)一直等待,直到隊(duì)列里面的任務(wù)完成之后再繼續(xù)執(zhí)行。
      2.只能在當(dāng)前已有線程中執(zhí)行任務(wù),不具備開啟新線程的能力。
  • 異步執(zhí)行(async)
    1. 異步添加任務(wù)到指定的隊(duì)列中,它不會(huì)做任何等待,可以繼續(xù)執(zhí)行任務(wù)。
    2.可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力。

注意:異步執(zhí)行(async) 雖然具有開啟新線程的能力,但是并不一定開啟新線程。這根任務(wù)所指定的隊(duì)列類型有關(guān)
隊(duì)列(Dispatch Queue):這里的隊(duì)列指執(zhí)行任務(wù)的等待隊(duì)列,既用來(lái)存放任務(wù)的隊(duì)列。隊(duì)列是一種特殊的線性表,采用FIFO(先進(jìn)先出)的原則,既新任務(wù)總是被插入到隊(duì)列的末尾,而讀取任務(wù)的時(shí)候總是從隊(duì)列的頭部開始讀取。每讀取一個(gè)任務(wù),則從隊(duì)列中釋放一個(gè)任務(wù)。

隊(duì)列(Dispatch Queue).png

在GCD中有兩種隊(duì)列:串行隊(duì)列和并發(fā)隊(duì)列。兩者都符合FIFO(先進(jìn)先出)的原則。兩者的主要區(qū)別是:執(zhí)行順序不同,以及開啟線程數(shù)不同。

  • 串行隊(duì)列(Serial Dispatch Queue):
    每次只有一個(gè)任務(wù)被執(zhí)行。讓任務(wù)一個(gè)接著一個(gè)執(zhí)行。(只開啟一個(gè)線程,一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù))
  • 并發(fā)隊(duì)列(Concurrent Dispatch Queue):
    可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行。(可以開啟多個(gè)線程,并且同時(shí)執(zhí)行任務(wù))

注意:并發(fā)隊(duì)列的并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效

image

image

3.GCD的使用步驟

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

3.1 隊(duì)列的創(chuàng)建方法/獲取方法

  • 可以使用 dispatch_queue_create 來(lái)創(chuàng)建隊(duì)列,需要傳入兩個(gè)參數(shù),第一個(gè)參數(shù)標(biāo)識(shí)隊(duì)列的唯一標(biāo)識(shí)符,用于DEBUG,可為空,Dispatch Queue的名稱推薦使用應(yīng)用程序ID這種逆序全程域名; 第二個(gè)參數(shù)用來(lái)識(shí)別是串行隊(duì)列還是并發(fā)隊(duì)列。 DISPATCH_QUEUE_SERIAL表示串行隊(duì)列,DISPATCH_QUEUE_CONCURRENT表示并發(fā)隊(duì)列。
    // 串行隊(duì)列創(chuàng)建
    dispatch_queue_t queue = dispatch_queue_create("net.ceshi.test", DISPATCH_QUEUE_SERIAL);
    // 并發(fā)隊(duì)列創(chuàng)建方法
    dispatch_queue_t queue = dispatch_queue_create("net.ceshi.test", DISPATCH_QUEUE_CONCURRENT);
  • 對(duì)于串行隊(duì)列,GCD提供了的一種特殊的串行隊(duì)列:主隊(duì)列(Main Dispatch Queue)
    1.所有放在主隊(duì)列中的任務(wù),都會(huì)放到主線程中執(zhí)行。
    2.可以使用dispatch_get_main_queue()獲得主隊(duì)列。
  • 對(duì)于并發(fā)隊(duì)列,GCD默認(rèn)提供了全局并發(fā)隊(duì)列 (Global Dispatch Queue)。
    可以使用dispatch_get_global_queue來(lái)獲取。需要傳入兩個(gè)參數(shù)。第一個(gè)參數(shù)表示隊(duì)列優(yōu)先級(jí),一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二個(gè)參數(shù)暫時(shí)沒(méi)用,用0即可。

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

GCD提供了同步執(zhí)行任務(wù)的創(chuàng)建方法dispatch_sync和異步執(zhí)行任務(wù)創(chuàng)建方法dispatch_async。

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

實(shí)際上剛才還說(shuō)了兩種特殊隊(duì)列:全局并發(fā)隊(duì)列、主隊(duì)列。全局并發(fā)隊(duì)列可以作為普通并發(fā)隊(duì)列來(lái)使用。但是主隊(duì)列比較特殊,所以又多了兩種組合方式。這樣就有六種不用的組合方式

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

區(qū)別 并發(fā)隊(duì)列 串行隊(duì)列 主隊(duì)列
同步 沒(méi)有開啟新線程,串行執(zhí)行任務(wù) 沒(méi)有開啟新線程,串行執(zhí)行任務(wù) 主線程調(diào)用:死鎖卡住不執(zhí)行。其他線程調(diào)用:沒(méi)有開啟新線程,串行執(zhí)行任務(wù)
異步 有開啟新線程,并發(fā)執(zhí)行任務(wù) 有開啟新線程(1條),串行執(zhí)行任務(wù) 沒(méi)有開啟新線程,串行執(zhí)行任務(wù)

4.GCD的基本使用

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

  • 在當(dāng)前線程中執(zhí)行任務(wù),不會(huì)開啟新線程,執(zhí)行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)。
/**
 同步執(zhí)行  + 并發(fā)隊(duì)列
 特點(diǎn) 在當(dāng)前線程中執(zhí)行任務(wù)不會(huì)開啟新線程,任務(wù)順序執(zhí)行
 */
- (void)syncConcurrent{
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
    NSLog(@"asyncConcurrent---begin");
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        [NSThread sleepForTimeInterval:1];              // 模擬耗時(shí)操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    });
    
    dispatch_sync(queue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];              // 模擬耗時(shí)操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    NSLog(@"syncConcurrent---end");
}
/*
打印結(jié)果 
currentThread---<NSThread: 0x600001aa53c0>{number = 1, name = main}
asyncConcurrent---begin
1---<NSThread: 0x600001aa53c0>{number = 1, name = main}
2---<NSThread: 0x600001aa53c0>{number = 1, name = main}
2---<NSThread: 0x600001aa53c0>{number = 1, name = main}
syncConcurrent---end
*/

總結(jié)同步執(zhí)行+并發(fā)隊(duì)列:

  • 所有任務(wù)都是在當(dāng)前線程(主線程中執(zhí)行),沒(méi)有開啟新的線程(同步執(zhí)行不具備開啟新線程的能力)
  • 所有任務(wù)都在打印的syncConcurrent---begin和syncConcurrent---end之間執(zhí)行的(同步任務(wù)需要等待隊(duì)列的任務(wù)執(zhí)行結(jié)束)。
  • 任務(wù)按順序執(zhí)行。

原文 :任務(wù)按順序執(zhí)行的。按順序執(zhí)行的原因:雖然并發(fā)隊(duì)列可以開啟多個(gè)線程,并且同時(shí)執(zhí)行多個(gè)任務(wù)。但是因?yàn)楸旧聿荒軇?chuàng)建新線程,只有當(dāng)前線程這一個(gè)線程(同步任務(wù)不具備開啟新線程的能力),所以也就不存在并發(fā)。而且當(dāng)前線程只有等待當(dāng)前隊(duì)列中正在執(zhí)行的任務(wù)執(zhí)行完畢之后,才能繼續(xù)接著執(zhí)行下面的操作(同步任務(wù)需要等待隊(duì)列的任務(wù)執(zhí)行結(jié)束)。所以任務(wù)只能一個(gè)接一個(gè)按順序執(zhí)行,不能同時(shí)被執(zhí)行。
作者:行走少年郎
鏈接:http://www.itdecent.cn/p/2d57c72016c6
來(lái)源:簡(jiǎn)書
簡(jiǎn)書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處。

上面這段話 我自己理解的意思是 并發(fā)隊(duì)列調(diào)度任務(wù)的時(shí)候,如果是同步任務(wù)則只會(huì)在當(dāng)前線程執(zhí)行,并不會(huì)開啟新線程。(其實(shí)并發(fā)是需要調(diào)度其他線程來(lái)執(zhí)行任務(wù)的) 一個(gè)線程同一時(shí)間只能處理一個(gè)任務(wù),所以任務(wù)只能一個(gè)個(gè)執(zhí)行。

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

  • 可以開啟多個(gè)線程,任務(wù)交替(同時(shí))執(zhí)行
- (void)asyncConcurrent{
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"asyncConcurrent---begin");
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];              // 模擬耗時(shí)操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    NSLog(@"asyncConcurrent---end");

    /*
     打印結(jié)果
     currentThread---<NSThread: 0x6000020b2900>{number = 1, name = main}
     asyncConcurrent---begin
     asyncConcurrent---end
     1---<NSThread: 0x6000020c9bc0>{number = 3, name = (null)}
     2---<NSThread: 0x6000020e3b00>{number = 4, name = (null)}
     2---<NSThread: 0x6000020e3b00>{number = 4, name = (null)}
     1---<NSThread: 0x6000020c9bc0>{number = 3, name = (null)}
     */
}

總結(jié)異步執(zhí)行+并發(fā)隊(duì)列:

  • 除了當(dāng)前線程(主線程),系統(tǒng)有開啟了3個(gè)線程,并且任務(wù)是交替同時(shí)執(zhí)行的。(異步執(zhí)行具備開啟線程的能力,并發(fā)隊(duì)列可以開啟多個(gè)線程,同時(shí)執(zhí)行多個(gè)任務(wù))
  • 所有任務(wù)是在打印的syncConcurrent---begin和syncConcurrent---end之后才執(zhí)行的。說(shuō)明當(dāng)前線程沒(méi)有等待異步任務(wù)執(zhí)行,而是直接開啟新線程執(zhí)行任務(wù)。

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

  • 不會(huì)開啟新線程,在當(dāng)前線程執(zhí)行任務(wù)。任務(wù)是串行的,執(zhí)行完一個(gè)任務(wù),在執(zhí)行下一個(gè)任務(wù)。
- (void)syncSerial{
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"syncSerial---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    dispatch_sync(queue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];              // 模擬耗時(shí)操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    NSLog(@"syncSerial---end");

    /*
  打印結(jié)果
     currentThread---<NSThread: 0x6000020b2900>{number = 1, name = main}
     syncSerial---begin
     1---<NSThread: 0x6000020b2900>{number = 1, name = main}
     1---<NSThread: 0x6000020b2900>{number = 1, name = main}
     2---<NSThread: 0x6000020b2900>{number = 1, name = main}
     2---<NSThread: 0x6000020b2900>{number = 1, name = main}
     syncSerial---end
     */
}

總結(jié):同步執(zhí)行 + 串行隊(duì)列

  • 所有任務(wù)都在主線程中執(zhí)行,沒(méi)有開啟新的線程(同步執(zhí)行不具備開啟新線程的能力)。
  • 所有任務(wù)都在打印的syncConcurrent---begin和syncConcurrent---end之間執(zhí)行(同步任務(wù)需要等待隊(duì)列的任務(wù)執(zhí)行結(jié)束)(需要等待)。

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

  • 會(huì)開啟新線程,但是因?yàn)槿蝿?wù)是串行的,任務(wù)順序執(zhí)行
- (void)asyncSerial{
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"asyncSerial---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    dispatch_async(queue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];              // 模擬耗時(shí)操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    NSLog(@"asyncSerial---end");
    
    /*
     currentThread---<NSThread: 0x6000020b2900>{number = 1, name = main}
     asyncSerial---begin
     asyncSerial---end
     1---<NSThread: 0x6000018dc000>{number = 3, name = (null)}
     1---<NSThread: 0x6000018dc000>{number = 3, name = (null)}
     2---<NSThread: 0x6000018dc000>{number = 3, name = (null)}
     2---<NSThread: 0x6000018dc000>{number = 3, name = (null)}
     */
}

總結(jié):異步執(zhí)行 + 串行隊(duì)列

  • 開啟了一條新線程(異步執(zhí)行具備開啟新線程的能力,串行隊(duì)列只開啟一條線程)。
  • 所有任務(wù)是在打印syncConcurrent---begin和syncConcurrent---end之后才開始執(zhí)行的(異步執(zhí)行不會(huì)做任何等待,可以繼續(xù)執(zhí)行任務(wù))。
  • 任務(wù)按順序執(zhí)行

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

  • 主隊(duì)列:GCD自帶的一種特殊的串行隊(duì)列
    同步執(zhí)行 + 主隊(duì)列在不同線程中調(diào)用結(jié)果也是不一樣,在主線程中調(diào)用會(huì)出現(xiàn)死鎖。在其他線程中則不會(huì)

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

  • 相互等待 卡死 崩潰
- (void)syncMain{
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"syncMain---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    NSLog(@"syncMain---end");
    /*
     c輸出結(jié)果
     currentThread---<NSThread: 0x600003d72880>{number = 1, name = main}
     syncMain---begin
     --- 崩潰
    */
}

總結(jié):同步執(zhí)行 + 主隊(duì)列

  • 在主線程中使用同步執(zhí)行 + 主隊(duì)列,追加到主線程的任務(wù)1 不執(zhí)行 直接崩潰,end也沒(méi)打印。
    這是因?yàn)槲覀冊(cè)僦骶€程中執(zhí)行syncMain方法,相當(dāng)于吧syncMain任務(wù)放到了主線程的隊(duì)列中。而同步執(zhí)行會(huì)等待當(dāng)前隊(duì)列中的任務(wù)執(zhí)行完畢,才會(huì)接著執(zhí)行。那么當(dāng)我們把任務(wù)1 追加到主隊(duì)列中,任務(wù)1就在等主線程處理完syncMain任務(wù)。而syncMain任務(wù)需要等待任務(wù)1 執(zhí)行完畢,才能接著執(zhí)行。
    要是如果不在主線程中調(diào)用,而在其他線程中調(diào)用會(huì)如何呢?

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

// 使用 NSThread 的 detachNewThreadSelector 方法會(huì)創(chuàng)建線程,并自動(dòng)啟動(dòng)線程執(zhí)行
 selector 任務(wù)
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
/*
打印結(jié)果
currentThread---<NSThread: 0x600002205a80>{number = 3, name = (null)}
syncMain---begin
1---<NSThread: 0x60000226a940>{number = 1, name = main}
1---<NSThread: 0x60000226a940>{number = 1, name = main}
syncMain---end
*/

總結(jié):在其他線程中使用同步執(zhí)行 + 主隊(duì)列

  • 所有任務(wù)都是在主線程(非當(dāng)前線程)中執(zhí)行的,沒(méi)有開啟新的線程(所有放在主隊(duì)列中的任務(wù),都會(huì)放到主線程中執(zhí)行)。
  • 所有任務(wù)都在打印的syncConcurrent---begin和syncConcurrent---end之間執(zhí)行(同步任務(wù)需要等待隊(duì)列的任務(wù)執(zhí)行結(jié)束)。
  • 任務(wù)是按順序執(zhí)行的(主隊(duì)列是串行隊(duì)列,每次只有一個(gè)任務(wù)被執(zhí)行,任務(wù)一個(gè)接一個(gè)按順序執(zhí)行)。
    為什么現(xiàn)在就不會(huì)卡主了呢?
    因?yàn)閟yncMain 任務(wù)放到了其他線程里,而任務(wù)1追加到主隊(duì)列中,這三個(gè)任務(wù)都會(huì)在主線程中執(zhí)行。syncMain 任務(wù)在其他線程中執(zhí)行到追加任務(wù)1到主隊(duì)列中時(shí),因?yàn)橹麝?duì)列現(xiàn)在沒(méi)有正在執(zhí)行的任務(wù),所以會(huì)直接執(zhí)行主隊(duì)列的任務(wù)1,等任務(wù)1執(zhí)行完畢,再接著執(zhí)行其他任務(wù)。所以這里不會(huì)卡主線程

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

  • 只在主線程中執(zhí)行任務(wù),任務(wù)順序執(zhí)行
/**
 * 異步執(zhí)行 + 主隊(duì)列
 * 特點(diǎn):只在主線程中執(zhí)行任務(wù),執(zhí)行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)
 */
- (void)asyncMain {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"asyncMain---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    NSLog(@"asyncMain---end");
/*
currentThread---<NSThread: 0x6000028d6880>{number = 1, name = main}
asyncMain---begin
asyncMain---end
1---<NSThread: 0x6000028d6880>{number = 1, name = main}
1---<NSThread: 0x6000028d6880>{number = 1, name = main}
2---<NSThread: 0x6000028d6880>{number = 1, name = main}
2---<NSThread: 0x6000028d6880>{number = 1, name = main}
3---<NSThread: 0x6000028d6880>{number = 1, name = main}
3---<NSThread: 0x6000028d6880>{number = 1, name = main}
*/
}

總結(jié):異步執(zhí)行 + 主隊(duì)列

  • 所有任務(wù)都是在當(dāng)前線程(主線程)中執(zhí)行的。并沒(méi)有開啟新的線程(雖然異步執(zhí)行具備開啟線程的能力,但因?yàn)槭侵麝?duì)列,所以所有任務(wù)都在主線程中)
  • 所有任務(wù)是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執(zhí)行的(異步執(zhí)行不會(huì)做任何等待,可以繼續(xù)執(zhí)行任務(wù))。
  • 任務(wù)是按順序執(zhí)行的(因?yàn)橹麝?duì)列是串行隊(duì)列,每次只有一個(gè)任務(wù)被執(zhí)行,任務(wù)一個(gè)接一個(gè)按順序執(zhí)行)。

GCD線程間的通信

在iOS開發(fā)過(guò)程中,我們一般在主線程里邊進(jìn)行UI刷新,例如:點(diǎn)擊、滾動(dòng)、拖拽等事件。我們通常把一些耗時(shí)的操作放在其他線程,比如說(shuō)圖片下載、文件上傳等耗時(shí)操作。而當(dāng)我們有時(shí)候在其他線程完成了耗時(shí)操作時(shí),需要回到主線程,那么就用到了線程之間的通訊。

/**
 * 線程間通信
 */
- (void)communication {
    // 獲取全局并發(fā)隊(duì)列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    // 獲取主隊(duì)列
    dispatch_queue_t mainQueue = dispatch_get_main_queue(); 
    
    dispatch_async(queue, ^{
        // 異步追加任務(wù)
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
        
        // 回到主線程
        dispatch_async(mainQueue, ^{
            // 追加在主線程中執(zhí)行的任務(wù)
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        });
    });
}

輸出結(jié)果:
2018-02-23 20:47:03.462394+0800 YSC-GCD-demo[20154:5053282] 1---<NSThread: 0x600000271940>{number = 3, name = (null)}
2018-02-23 20:47:05.465912+0800 YSC-GCD-demo[20154:5053282] 1---<NSThread: 0x600000271940>{number = 3, name = (null)}
2018-02-23 20:47:07.466657+0800 YSC-GCD-demo[20154:5052953] 2---<NSThread: 0x60000007bf80>{number = 1, name = main}
作者:行走少年郎
鏈接:http://www.itdecent.cn/p/2d57c72016c6

6 GCD的其他方法

6.1 GCD 柵欄方法:dispatch_barrier_async

  • 有時(shí)我們需要異步執(zhí)行兩組操作,而且第一組操作執(zhí)行完之后,才能開啟執(zhí)行第二組操作。這樣我們就需要一個(gè)相當(dāng)于柵欄一樣的方法將兩組異步執(zhí)行的操作組給分割起來(lái),當(dāng)然這里的操作組里可以包含一個(gè)或者多個(gè)任務(wù)。這就需要用到** dispatch_barrier_async**方法在兩個(gè)操作組間形成柵欄。


    image
- (void)barrier{
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"%d---%@",i,[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    dispatch_async(queue, ^{
        // 追加任務(wù)1
        for (int i = 2; i < 4; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"%d---%@",i,[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    dispatch_barrier_async(queue, ^{
        // 追加任務(wù) barrier
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印當(dāng)前線程
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任務(wù)3
        for (int i = 4; i < 6; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"%d---%@",i,[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任務(wù)3
        for (int i = 6; i < 8; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"%d---%@",i,[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
}

打印結(jié)果

2019-07-03 21:24:58.105410+0800 二叉樹練習(xí)[34879:618905] 0---<NSThread: 0x600003ed4200>{number = 3, name = (null)}
2019-07-03 21:24:58.105408+0800 二叉樹練習(xí)[34879:618908] 2---<NSThread: 0x600003efb5c0>{number = 4, name = (null)}
2019-07-03 21:25:00.110444+0800 二叉樹練習(xí)[34879:618908] 3---<NSThread: 0x600003efb5c0>{number = 4, name = (null)}
2019-07-03 21:25:00.110444+0800 二叉樹練習(xí)[34879:618905] 1---<NSThread: 0x600003ed4200>{number = 3, name = (null)}
2019-07-03 21:25:02.113756+0800 二叉樹練習(xí)[34879:618905] barrier---<NSThread: 0x600003ed4200>{number = 3, name = (null)}
2019-07-03 21:25:04.118678+0800 二叉樹練習(xí)[34879:618905] barrier---<NSThread: 0x600003ed4200>{number = 3, name = (null)}
2019-07-03 21:25:06.123982+0800 二叉樹練習(xí)[34879:618908] 6---<NSThread: 0x600003efb5c0>{number = 4, name = (null)}
2019-07-03 21:25:06.123982+0800 二叉樹練習(xí)[34879:618905] 4---<NSThread: 0x600003ed4200>{number = 3, name = (null)}
2019-07-03 21:25:08.128269+0800 二叉樹練習(xí)[34879:618905] 5---<NSThread: 0x600003ed4200>{number = 3, name = (null)}
2019-07-03 21:25:08.128295+0800 二叉樹練習(xí)[34879:618908] 7---<NSThread: 0x600003efb5c0>{number = 4, name = (null)}

  • 總結(jié)
  • 柵欄方法的執(zhí)行順序?yàn)? 柵欄方法之前添加的任務(wù)組 --> 柵欄方法的任務(wù) -->柵欄方法之后添加的任務(wù)組

6.2 GCD 延時(shí)執(zhí)行方法:dispatch_after (常用的不抄了)

6.3 GCD 一次性代碼(只執(zhí)行一次):dispatch_once (常用的不抄了)

6.4 GCD 快速迭代方法:dispatch_apply

  • 通常我們會(huì)用for循環(huán)遍歷,但是GCD給我們提供了快速迭代的函數(shù)dispatch_apply、它會(huì)按照我們指定的次數(shù)將指定的任務(wù)追加到指定的隊(duì)列中,并等待全部隊(duì)列執(zhí)行結(jié)束。
  • 如果是在串行隊(duì)列中使用dispatch_apply,會(huì)和for循環(huán)一樣,按順序同步執(zhí)行,這樣就體現(xiàn)不出快速迭代的意義了。我們可以利用并發(fā)隊(duì)列進(jìn)行異步執(zhí)行。比如遍歷0--5 這6個(gè)數(shù)字。for 循環(huán)的做法是每次取出一個(gè)元素,逐個(gè)遍歷。dispatch_apply 可以 在多個(gè)線程中同時(shí)(異步)遍歷多個(gè)數(shù)字。
    還有一點(diǎn),無(wú)論是在串行隊(duì)列,還是并發(fā)隊(duì)列中,dispatch_apply 都會(huì)等待全部任務(wù)執(zhí)行完畢,這點(diǎn)就像是同步操作,也像是隊(duì)列組中的 dispatch_group_wait方法。
/**
 * 快速迭代方法 dispatch_apply
 */
- (void)apply {
    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");
}

因?yàn)槭窃诓l(fā)隊(duì)列中異步執(zhí)行任務(wù),所以各個(gè)任務(wù)的執(zhí)行時(shí)間長(zhǎng)短不定,最后結(jié)束順序也不定。但是apply---end一定在最后執(zhí)行。這是因?yàn)閐ispatch_apply函數(shù)會(huì)等待全部任務(wù)執(zhí)行完畢。

6.5 GCD隊(duì)列組:dispatch_group

有時(shí)候我們會(huì)有這樣的需求:分別異步執(zhí)行2個(gè)耗時(shí)任務(wù),然后當(dāng)2個(gè)耗時(shí)任務(wù)都執(zhí)行完畢后再回到主線程執(zhí)行任務(wù)。這時(shí)候我們可以用到GCD的隊(duì)列組。

  • 調(diào)用隊(duì)列組的dispatch_group_async 先把任務(wù)放到隊(duì)列中,然后將隊(duì)列放入隊(duì)列組中,或者使用隊(duì)列組的dispatch_group_enter、dispatch_group_leave 組合 來(lái)實(shí)現(xiàn)dispatch_group_async。
  • 調(diào)用隊(duì)列組的dispatch_group_notify 回到指定線程執(zhí)行任務(wù)?;蛘呤褂胐ispatch_group_wait 回到當(dāng)前線程繼續(xù)向下執(zhí)行(會(huì)阻塞當(dāng)前線程)

6.5.1 dispatch_group_notify

  • 監(jiān)聽group中任務(wù)的完成狀態(tài),當(dāng)所有的任務(wù)都執(zhí)行完成后,追加任務(wù)到group中,并執(zhí)行任務(wù)。
- (void)groupNotify{
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"group---begin");
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的異步任務(wù)1、任務(wù)2都執(zhí)行完畢后,回到主線程執(zhí)行下邊任務(wù)
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
        NSLog(@"group---end");
    });
}
/* 
打印結(jié)果
currentThread---<NSThread: 0x600002722940>{number = 1, name = main}
group---begin
2---<NSThread: 0x600002750bc0>{number = 4, name = (null)}
1---<NSThread: 0x600002758980>{number = 3, name = (null)}
1---<NSThread: 0x600002758980>{number = 3, name = (null)}
2---<NSThread: 0x600002750bc0>{number = 4, name = (null)}
3---<NSThread: 0x600002722940>{number = 1, name = main}
3---<NSThread: 0x600002722940>{number = 1, name = main}
group---end
*/

總結(jié):當(dāng)所有隊(duì)列組中的任務(wù)完成之后,才執(zhí)行dispatch_group_notify block中的任務(wù)。

6.5.2 dispatch_group_wait

  • 暫停當(dāng)前線程(阻塞當(dāng)前線程),等待指定的group中的任務(wù)執(zhí)行完成后,才會(huì)往下繼續(xù)執(zhí)行。

6.5.3 dispatch_group_enter、dispatch_group_leave

  • dispatch_group_enter 標(biāo)志著一個(gè)任務(wù)追加到 group,執(zhí)行一次,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù)+1
  • dispatch_group_leave 標(biāo)志著一個(gè)任務(wù)離開了 group,執(zhí)行一次,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù)-1。
  • 當(dāng) group 中未執(zhí)行完畢任務(wù)數(shù)為0的時(shí)候,才會(huì)使dispatch_group_wait解除阻塞,以及執(zhí)行追加到dispatch_group_notify中的任務(wù)。

原文

/**
 * 隊(duì)列組 dispatch_group_enter、dispatch_group_leave
 */
- (void)groupEnterAndLeave
{
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    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, ^{
        // 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的異步操作都執(zhí)行完畢后,回到主線程.
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
        NSLog(@"group---end");
    });
    
//    // 等待上面的任務(wù)全部完成后,會(huì)往下繼續(xù)執(zhí)行(會(huì)阻塞當(dāng)前線程)
//    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//
//    NSLog(@"group---end");
}

輸出結(jié)果:
2018-02-23 22:14:17.997667+0800 YSC-GCD-demo[20592:5214830] currentThread---<NSThread: 0x604000066600>{number = 1, name = main}
2018-02-23 22:14:17.997839+0800 YSC-GCD-demo[20592:5214830] group---begin
2018-02-23 22:14:20.000298+0800 YSC-GCD-demo[20592:5215094] 1---<NSThread: 0x600000277c80>{number = 4, name = (null)}
2018-02-23 22:14:20.000305+0800 YSC-GCD-demo[20592:5215095] 2---<NSThread: 0x600000277c40>{number = 3, name = (null)}
2018-02-23 22:14:22.001323+0800 YSC-GCD-demo[20592:5215094] 1---<NSThread: 0x600000277c80>{number = 4, name = (null)}
2018-02-23 22:14:22.001339+0800 YSC-GCD-demo[20592:5215095] 2---<NSThread: 0x600000277c40>{number = 3, name = (null)}
2018-02-23 22:14:24.002321+0800 YSC-GCD-demo[20592:5214830] 3---<NSThread: 0x604000066600>{number = 1, name = main}
2018-02-23 22:14:26.002852+0800 YSC-GCD-demo[20592:5214830] 3---<NSThread: 0x604000066600>{number = 1, name = main}
2018-02-23 22:14:26.003116+0800 YSC-GCD-demo[20592:5214830] group---end

從dispatch_group_enter、dispatch_group_leave相關(guān)代碼運(yùn)行結(jié)果中可以看出:當(dāng)所有任務(wù)執(zhí)行完成之后,才執(zhí)行 dispatch_group_notify 中的任務(wù)。這里的dispatch_group_enter、dispatch_group_leave組合,其實(shí)等同于dispatch_group_async。

我們自己在用隊(duì)列組的情況通常是 發(fā)起多個(gè)異步的網(wǎng)絡(luò)請(qǐng)求,等請(qǐng)求全部完成后刷新UI,這個(gè)時(shí)候?yàn)榱诵?保證等多個(gè)異步請(qǐng)求完成后再刷新UI 經(jīng)常會(huì)用dispatch_group_enter、dispatch_group_leave組合,如果只用dispatch_group_async則達(dá)不到這種效果。

/*
如果去掉ispatch_group_enter、dispatch_group_leave組合,則dispatch_group_async就不好使了。
*/
- (void)groupNotify{
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"group---begin");
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任務(wù)1
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
            }
            dispatch_group_leave(group);

        });
      
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任務(wù)2
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
            }
            dispatch_group_leave(group);

        });
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的異步任務(wù)1、任務(wù)2都執(zhí)行完畢后,回到主線程執(zhí)行下邊任務(wù)
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
        NSLog(@"group---end");
    });
}

6.6 GCD 信號(hào)量:dispatch_semaphore

GCD中的信號(hào)量是指dispatch semaphore 是持有計(jì)數(shù)的信號(hào)。類似于過(guò)高速路收費(fèi)站的欄桿。可以通過(guò)時(shí),打開欄桿,不可以通過(guò)時(shí),關(guān)閉欄桿。在dispatch semaphore 中,使用計(jì)數(shù)來(lái)完成這個(gè)功能,計(jì)數(shù)小于0時(shí)等待,不可通過(guò)。計(jì)數(shù)為0或大于0時(shí),計(jì)數(shù)減1且不等待,可通過(guò)。
dispatch_semaphore 提供了三個(gè)函數(shù)。

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

dispatch_semaphore_wait
// 等待降低信號(hào)量,接收一個(gè)信號(hào)和時(shí)間值(多為DISPATCH_TIME_FOREVER)
// 若信號(hào)的信號(hào)量為0,則會(huì)阻塞當(dāng)前線程,直到信號(hào)量大于0或者經(jīng)過(guò)輸入的時(shí)間值;
// 若信號(hào)量大于0,則會(huì)使信號(hào)量減1并返回,程序繼續(xù)住下執(zhí)行

信號(hào)量的使用前提是:想清楚你需要處理哪個(gè)線程等待(阻塞),又要哪個(gè)線程繼續(xù)執(zhí)行,然后使用信號(hào)量。
Dispatch Semaphore 在實(shí)際開發(fā)中主要用于:

  • 保持線程同步,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)
  • 保證線程安全,為線程加鎖

6.6.1 Dispatch Semaphore 線程同步

我們?cè)陂_發(fā)中會(huì)遇到這樣的需求:異步執(zhí)行耗時(shí)任務(wù),并使用異步執(zhí)行的結(jié)果進(jìn)行一些額外的操作。換句話說(shuō),相當(dāng)于,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)。比如說(shuō):AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通過(guò)引入信號(hào)量的方式,等待異步執(zhí)行任務(wù)結(jié)果,獲取到 tasks,然后再返回該 tasks

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }

        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;
}

下面我們來(lái)利用Dispatch Semaphore 實(shí)現(xiàn)線程同步,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)。

- (void)semaphoreSync{
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    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, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        
        number = 100;
        
        dispatch_semaphore_signal(semaphore);
    });
    NSLog(@"semaphore--number = %d",number);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %d",number);
 /*
  打印結(jié)果:
currentThread---<NSThread: 0x600002452940>{number = 1, name = main}
semaphore---begin
semaphore---end,number = 0
1---<NSThread: 0x600002434f40>{number = 3, name = (null)}
semaphore---end,number = 100
*/
}

總結(jié):

  • 打印結(jié)果 semaphore---end,number = 0 證明 異步執(zhí)行沒(méi)有等待,semaphore---end,number = 100 證明dispatch_semaphore_wait 是在等待 異步任務(wù)的。
    任務(wù)執(zhí)行順序如下:
  1. semaphore 初始創(chuàng)建時(shí)計(jì)數(shù)為 0。
    2.異步執(zhí)行將任務(wù) 1 追加到隊(duì)列之后,不做等待,接著執(zhí)行
    3.打印 semaphore---end,number = 0
    4.dispatch_semaphore_wait方法,semaphore 減 1,此時(shí) semaphore == -1,當(dāng)前線程進(jìn)入等待狀態(tài)。
    5.然后,異步任務(wù) 1 開始執(zhí)行。任務(wù)1執(zhí)行到dispatch_semaphore_signal之后,總信號(hào)量加1,此時(shí) semaphore == 0,正在被阻塞的線程(主線程)恢復(fù)繼續(xù)執(zhí)行。
    6.最后打印semaphore---end,number = 100

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

6.6.2 Dispatch Semaphore 線程安全和線程同步(為線程加鎖)

線程安全: 如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期是一樣的,就是線程安全的。
若每個(gè)線程中對(duì)全局變量。靜態(tài)變量只有讀操作,而無(wú)寫操作,一般來(lái)說(shuō)這個(gè)全局變量是線程安全的。若有多個(gè)線程通知執(zhí)行寫操作(更改變量),一般都需要考慮線程同步,否則的話就可能影響線程安全。
線程同步:可理解為線程A和線程B一塊配合,A執(zhí)行到一定程度時(shí) 要依靠線程B的某個(gè)結(jié)果,于是停下來(lái),示意B運(yùn)行,B去執(zhí)行,再將接活給A,A繼續(xù)執(zhí)行。

模擬火車票售賣,實(shí)現(xiàn)線程安全和解決線程同步問(wèn)題。

  • 場(chǎng)景:總共有50張火車票,有兩個(gè)售賣火車票的窗口,一個(gè)是北京火車票售賣窗口,另一個(gè)是上?;疖嚻笔圪u窗口。兩個(gè)窗口同時(shí)售賣火車票,賣完為止。

6.6.2.1 非線程安全(不使用semaphore)

先看看不考慮線程安全的代碼:

/**
 * 非線程安全:不使用 semaphore
 * 初始化火車票數(shù)量、賣票窗口(非線程安全)、并開始賣票
 */
- (void)initTicketStatusNotSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    NSLog(@"semaphore---begin");
    
    self.ticketSurplusCount = 50;
    
    // queue1 代表北京火車票售賣窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表上?;疖嚻笔圪u窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketNotSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf saleTicketNotSafe];
    });
}

/**
 * 售賣火車票(非線程安全)
 */
- (void)saleTicketNotSafe {
    while (1) {
        
        if (self.ticketSurplusCount > 0) {  //如果還有票,繼續(xù)售賣
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票數(shù):%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { //如果已賣完,關(guān)閉售票窗口
            NSLog(@"所有火車票均已售完");
            break;
        }
        
    }
}

打印結(jié)果
剩余票數(shù):8 窗口:<NSThread: 0x6000002b7040>{number = 4, name = (null)}
剩余票數(shù):8 窗口:<NSThread: 0x600000295c40>{number = 3, name = (null)}
剩余票數(shù):7 窗口:<NSThread: 0x6000002b7040>{number = 4, name = (null)}
剩余票數(shù):6 窗口:<NSThread: 0x600000295c40>{number = 3, name = (null)}
剩余票數(shù):4 窗口:<NSThread: 0x6000002b7040>{number = 4, name = (null)}
剩余票數(shù):5 窗口:<NSThread: 0x600000295c40>{number = 3, name = (null)}

  • 加鎖的情況
- (void)saleTicketSafe{
    while (1) {
        // 相當(dāng)于加鎖
        // 原始信號(hào)量值為1  dispatch_semaphore_wait后減一 此時(shí)值為0  繼續(xù)往下執(zhí)行
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
        if (self.ticketSurplusCount > 0) {  //如果還有票,繼續(xù)售賣
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票數(shù):%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { //如果已賣完,關(guān)閉售票窗口
            NSLog(@"所有火車票均已售完");
            // 相當(dāng)于解鎖
            // 當(dāng)前信號(hào)量值為0  dispatch_semaphore_signal后值加1 此時(shí)值為1  繼續(xù)往下執(zhí)行
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }
        // 相當(dāng)于解鎖
        // 當(dāng)前信號(hào)量值為0  dispatch_semaphore_signal后值加1 此時(shí)值為1  繼續(xù)往下執(zhí)行
        dispatch_semaphore_signal(semaphoreLock);
    }
}

打印結(jié)果
剩余票數(shù):4 窗口:<NSThread: 0x600001afb440>{number = 4, name = (null)}
剩余票數(shù):3 窗口:<NSThread: 0x600001acc440>{number = 3, name = (null)}
剩余票數(shù):2 窗口:<NSThread: 0x600001afb440>{number = 4, name = (null)}
剩余票數(shù):1 窗口:<NSThread: 0x600001acc440>{number = 3, name = (null)}
剩余票數(shù):0 窗口:<NSThread: 0x600001afb440>{number = 4, name = (null)}
所有火車票均已售完

最后重申一遍 此篇文章是對(duì)著iOS 多線程:『GCD』詳盡總結(jié) 此文抄的,勿噴。給自己加深映像用。具體請(qǐng)看上面的鏈接。 特別感謝 行走少年郎 的文章

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

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