GCD

原文:http://www.itdecent.cn/p/2d57c72016c6

概念

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

優(yōu)點

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

任務(wù)和隊列

任務(wù)

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

  • 同步執(zhí)行(sync)
    同步添加任務(wù)到指定的隊列中,在添加的任務(wù)執(zhí)行結(jié)束之前,會一直等待,直到隊列里面的任務(wù)完成之后再繼續(xù)執(zhí)行。只能在當前線程中執(zhí)行任務(wù),不具備開啟新線程的能力。
  • 異步執(zhí)行(async)
    異步添加任務(wù)到指定的隊列中,它不會做任何等待,可以繼續(xù)執(zhí)行任務(wù)??梢栽谛碌木€程中執(zhí)行任務(wù),具備開啟新線程的能力。

注意:異步執(zhí)行(async)雖然具有開啟新線程的能力,但是并不一定開啟新線程。這跟任務(wù)所指定的隊列類型有關(guān)(下面會講)。

隊列

這里的隊列指執(zhí)行任務(wù)的等待隊列,即用來存放任務(wù)的隊列。隊列是一種特殊的線性表,采用 FIFO(先進先出)的原則,即新任務(wù)總是被插入到隊列的末尾,而讀取任務(wù)的時候總是從隊列的頭部開始讀取。每讀取一個任務(wù),則從隊列中釋放一個任務(wù)。
GCD 中的任務(wù)有兩種:串行隊列并發(fā)隊列,兩者都符合先入先出原則,區(qū)別在于:執(zhí)行順序不同和開啟線程數(shù)不同。

  • 串行隊列(Serial Dispatch Queue)
    只開啟一個線程,每次只執(zhí)行一個任務(wù),執(zhí)行完這個任務(wù)繼續(xù)下一個任務(wù)。
  • 并發(fā)隊列(Concurrent Dispatch Queue)
    可以在多個線程中,讓多個任務(wù)并發(fā)(同時)執(zhí)行。注意:并發(fā)隊列的并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效

兩個重點

隊列與線程的關(guān)系:

主隊列(main_queue)就是在主線程中的默認隊列。并發(fā)隊列(global_queue)就是新開辟線程中的隊列。
一個線程中可以有多個串行的隊列,但是無法擁有多個并發(fā)的隊列,多個并發(fā)的隊列會放在多個線程中。例如可以自定義隊列加到主線程中同步執(zhí)行,中斷主隊列任務(wù)執(zhí)行完自定義隊列中任務(wù)后繼續(xù)執(zhí)行主隊列任務(wù)。

異步與并發(fā)的區(qū)別:

異步,是指執(zhí)行順序上,區(qū)別于同步的由上至下,異步會延后執(zhí)行異步內(nèi)的任務(wù)。而并發(fā)是指時間上的,不同于串行的等待,并發(fā)會同時執(zhí)行并發(fā)隊列中的任務(wù)。

GCD的使用步驟

GCD的使用有兩步,第一步創(chuàng)建一個隊列(串行隊列或者并行隊列),第二步將任務(wù)加入到等待隊列中。

創(chuàng)建隊列

通常使用dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)來創(chuàng)建隊列。第一個參數(shù)表示隊列的唯一標識符,通常用逆序全程域名;第二個參數(shù)用來識別是串行隊列還是并發(fā)隊列:DISPATCH_QUEUE_SERIAL 表示串行隊列,DISPATCH_QUEUE_CONCURRENT 表示并發(fā)隊列。

//創(chuàng)建串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("com.dispatch.cp", DISPATCH_QUEUE_SERIAL);
//創(chuàng)建并發(fā)隊列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.dispatch.cp", DISPATCH_QUEUE_CONCURRENT);
獲取隊列

對于串行隊列,GCD 提供了一種默認的特殊的串行隊列:主隊列(Main Dispatch Queue)。
所有放在主隊列中的任務(wù),都會放到主線程中執(zhí)行??墒褂?dispatch_get_main_queue() 獲得主隊列。

對于并發(fā)隊列,GCD 供了一種默認的特殊的并發(fā)隊列:全局并發(fā)隊列(Global Dispatch Queue)。
可以使用 dispatch_get_global_queue 來獲取。需要傳入兩個參數(shù)。第一個參數(shù)表示隊列優(yōu)先級,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二個參數(shù)暫時沒用,用0即可。

//獲取默認串行隊列(主隊列)
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//獲取默認并發(fā)隊列 (全局并發(fā)隊列)
dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
創(chuàng)建任務(wù)

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

//創(chuàng)建同步任務(wù)
dispatch_sync(<#dispatch_queue_t  _Nonnull queue#>, ^{
    //這里放任務(wù)的執(zhí)行方法
})
    
//創(chuàng)建異步任務(wù)
dispatch_async(<#dispatch_queue_t  _Nonnull queue#>, ^{
    //這里放任務(wù)的執(zhí)行方法
})

雖然使用 GCD 只需兩步,但是既然我們有兩種隊列(串行隊列/并發(fā)隊列),兩種任務(wù)執(zhí)行方式(同步執(zhí)行/異步執(zhí)行),那么我們就有了四種不同的組合方式。實際上,剛才還說了兩種特殊隊列:全局并發(fā)隊列、主隊列。全局并發(fā)隊列可以作為普通并發(fā)隊列來使用。但是主隊列因為有點特殊,所以我們就又多了兩種組合方式。這樣就有六種不同的組合方式了。

  • 同步執(zhí)行 + 并發(fā)隊列
    無效。同步無法開啟新線程,不能實現(xiàn)并發(fā)。
  • 異步執(zhí)行 + 并發(fā)隊列
    多線程的常用模式,異步并發(fā),多線程同時進行多個任務(wù)。
  • 同步執(zhí)行 + 串行隊列
    普通串行,沒有用到多線程。
  • 異步執(zhí)行 + 串行隊列
    多線程的常用模式,異步串行,新建一個線程串行執(zhí)行多個任務(wù)。
  • 同步執(zhí)行 + 主隊列
    無效。新建任務(wù)需等待主隊列任務(wù)結(jié)束,主隊列后續(xù)任務(wù)需等待新建任務(wù),互相等待,代碼卡死崩潰。
  • 異步執(zhí)行 + 主隊列
    類似異步串行,只是沒有新開辟線程,在主線程實現(xiàn)異步。

GCD六種組合下的情況

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

代碼:

- (void)syncConcurrent {
    NSLog(@"currentThread---%@",[NSThread currentThread]); //打印當前線程
    NSLog(@"syncConcurrent---begin");
    //創(chuàng)建并發(fā)隊列
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    //同步任務(wù) + 并發(fā)隊列
    dispatch_sync(queue, ^{
        // 自定義任務(wù)1
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2]; //模擬耗時操作
            NSLog(@"任務(wù)1---%@",[NSThread currentThread]); //打印當前線程
            
        }
        
    });
    dispatch_sync(queue, ^{
        // 自定義任務(wù)2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2]; //模擬耗時操作
            NSLog(@"任務(wù)2---%@",[NSThread currentThread]);// 打印當前線程
            
        }
        
    });
    dispatch_sync(queue, ^
    {
        // 自定義任務(wù)3
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2]; //模擬耗時操作
            NSLog(@"任務(wù)3---%@",[NSThread currentThread]);// 打印當前線程
            
        }
        
    });
    NSLog(@"syncConcurrent---end");
    
}

log:

2018-04-08 15:07:13.433670+0800 應(yīng)用加載時間的優(yōu)化[5701:894068] currentThread---<NSThread: 0x600000076180>{number = 1, name = main}
2018-04-08 15:07:13.434007+0800 應(yīng)用加載時間的優(yōu)化[5701:894068] syncConcurrent---begin
2018-04-08 15:07:15.436162+0800 應(yīng)用加載時間的優(yōu)化[5701:894068] 任務(wù)1---<NSThread: 0x600000076180>{number = 1, name = main}
2018-04-08 15:07:17.437191+0800 應(yīng)用加載時間的優(yōu)化[5701:894068] 任務(wù)1---<NSThread: 0x600000076180>{number = 1, name = main}
2018-04-08 15:07:19.438426+0800 應(yīng)用加載時間的優(yōu)化[5701:894068] 任務(wù)2---<NSThread: 0x600000076180>{number = 1, name = main}
2018-04-08 15:07:21.439689+0800 應(yīng)用加載時間的優(yōu)化[5701:894068] 任務(wù)2---<NSThread: 0x600000076180>{number = 1, name = main}
2018-04-08 15:07:23.441261+0800 應(yīng)用加載時間的優(yōu)化[5701:894068] 任務(wù)3---<NSThread: 0x600000076180>{number = 1, name = main}
2018-04-08 15:07:25.442858+0800 應(yīng)用加載時間的優(yōu)化[5701:894068] 任務(wù)3---<NSThread: 0x600000076180>{number = 1, name = main}
2018-04-08 15:07:25.443222+0800 應(yīng)用加載時間的優(yōu)化[5701:894068] syncConcurrent---end

可以看到線程數(shù)一直為1,當前線程一直為主線程
總結(jié):并發(fā)隊列本身不能創(chuàng)建線程,而同步任務(wù)同樣沒有創(chuàng)建線程。因此一直為主線程同步效果,沒有實現(xiàn)并發(fā)。

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

代碼:

- (void)asyncConcurrent {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印當前線程
    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:2]; //模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    dispatch_async(queue, ^{
        // 自定義任務(wù)2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2]; //模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印當前線程
            
        }
        
    });
    dispatch_async(queue, ^{
        // 自定義任務(wù)3
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2]; //模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);// 打印當前線程
        }
        
    });
    NSLog(@"asyncConcurrent---end");
}

log:

2018-04-08 22:01:41.497508+0800 應(yīng)用加載時間的優(yōu)化[6217:973574] currentThread---<NSThread: 0x604000078780>{number = 1, name = main}
2018-04-08 22:01:41.497657+0800 應(yīng)用加載時間的優(yōu)化[6217:973574] asyncConcurrent---begin
2018-04-08 22:01:41.498239+0800 應(yīng)用加載時間的優(yōu)化[6217:973574] asyncConcurrent---end
2018-04-08 22:01:44.622924+0800 應(yīng)用加載時間的優(yōu)化[6217:973669] 3---<NSThread: 0x600000277200>{number = 4, name = (null)}
2018-04-08 22:01:44.622929+0800 應(yīng)用加載時間的優(yōu)化[6217:973666] 2---<NSThread: 0x604000463440>{number = 3, name = (null)}
2018-04-08 22:01:46.624147+0800 應(yīng)用加載時間的優(yōu)化[6217:973667] 1---<NSThread: 0x604000461f40>{number = 5, name = (null)}
2018-04-08 22:01:46.624234+0800 應(yīng)用加載時間的優(yōu)化[6217:973666] 2---<NSThread: 0x604000463440>{number = 3, name = (null)}
2018-04-08 22:01:46.624266+0800 應(yīng)用加載時間的優(yōu)化[6217:973669] 3---<NSThread: 0x600000277200>{number = 4, name = (null)}
2018-04-08 22:01:48.626508+0800 應(yīng)用加載時間的優(yōu)化[6217:973667] 1---<NSThread: 0x604000461f40>{number = 5, name = (null)}

總結(jié):可以看到開辟了四個線程(不知道為什么不是三個),任務(wù)交替/同時進行。異步擁有開啟新線程的能力,并發(fā)執(zhí)行任務(wù)。第一時間在主線程中執(zhí)行了syncConcurrent---beginsyncConcurrent---end ,然后在開啟的三個線程中同時執(zhí)行自定義的打印任務(wù),然后在延遲三秒鐘后再同時打印第二次。

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

代碼:

- (void)syncSerial {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印當前線程
    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:2];// 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印當前線程
            
        }
    });
    dispatch_sync(queue, ^{
        // 追加任務(wù)2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印當前線程
        }
        
    });
    dispatch_sync(queue, ^{
        // 追加任務(wù)3
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    NSLog(@"syncSerial---end");
}

log:

2018-04-08 22:27:00.564340+0800 應(yīng)用加載時間的優(yōu)化[6278:994824] currentThread---<NSThread: 0x604000078d40>{number = 1, name = main}
2018-04-08 22:27:00.564452+0800 應(yīng)用加載時間的優(yōu)化[6278:994824] syncSerial---begin
2018-04-08 22:27:02.565885+0800 應(yīng)用加載時間的優(yōu)化[6278:994824] 1---<NSThread: 0x604000078d40>{number = 1, name = main}
2018-04-08 22:27:04.566469+0800 應(yīng)用加載時間的優(yōu)化[6278:994824] 1---<NSThread: 0x604000078d40>{number = 1, name = main}
2018-04-08 22:27:06.568109+0800 應(yīng)用加載時間的優(yōu)化[6278:994824] 2---<NSThread: 0x604000078d40>{number = 1, name = main}
2018-04-08 22:27:08.569092+0800 應(yīng)用加載時間的優(yōu)化[6278:994824] 2---<NSThread: 0x604000078d40>{number = 1, name = main}
2018-04-08 22:27:10.569759+0800 應(yīng)用加載時間的優(yōu)化[6278:994824] 3---<NSThread: 0x604000078d40>{number = 1, name = main}
2018-04-08 22:27:12.571093+0800 應(yīng)用加載時間的優(yōu)化[6278:994824] 3---<NSThread: 0x604000078d40>{number = 1, name = main}
2018-04-08 22:27:12.571398+0800 應(yīng)用加載時間的優(yōu)化[6278:994824] syncSerial---end

總結(jié):所有任務(wù)都在主線程中,沒有開辟新的線程。任務(wù)由上至下,間隔兩秒,依次執(zhí)行。

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

代碼:

- (void)asyncSerial {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印當前線程
    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:2];// 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    dispatch_async(queue, ^{
        // 追加任務(wù)2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    dispatch_async(queue, ^{
        // 追加任務(wù)3
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    NSLog(@"asyncSerial---end");
}

log:

2018-04-08 22:33:07.135609+0800 應(yīng)用加載時間的優(yōu)化[6314:1001768] currentThread---<NSThread: 0x604000073b80>{number = 1, name = main}
2018-04-08 22:33:07.136200+0800 應(yīng)用加載時間的優(yōu)化[6314:1001768] asyncSerial---begin
2018-04-08 22:33:07.136992+0800 應(yīng)用加載時間的優(yōu)化[6314:1001768] asyncSerial---end
2018-04-08 22:33:09.137247+0800 應(yīng)用加載時間的優(yōu)化[6314:1001860] 1---<NSThread: 0x60400027b7c0>{number = 3, name = (null)}
2018-04-08 22:33:11.142105+0800 應(yīng)用加載時間的優(yōu)化[6314:1001860] 1---<NSThread: 0x60400027b7c0>{number = 3, name = (null)}
2018-04-08 22:33:13.143027+0800 應(yīng)用加載時間的優(yōu)化[6314:1001860] 2---<NSThread: 0x60400027b7c0>{number = 3, name = (null)}
2018-04-08 22:33:15.145256+0800 應(yīng)用加載時間的優(yōu)化[6314:1001860] 2---<NSThread: 0x60400027b7c0>{number = 3, name = (null)}
2018-04-08 22:33:17.147141+0800 應(yīng)用加載時間的優(yōu)化[6314:1001860] 3---<NSThread: 0x60400027b7c0>{number = 3, name = (null)}
2018-04-08 22:33:19.151007+0800 應(yīng)用加載時間的優(yōu)化[6314:1001860] 3---<NSThread: 0x60400027b7c0>{number = 3, name = (null)}

總結(jié):在主線程中第一時間syncConcurrent---beginsyncConcurrent---end,然后在開啟的新線程中串行執(zhí)行自定義任務(wù),依次間隔兩秒。

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

代碼:

- (void)syncMain
{
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印當前線程
    NSLog(@"syncMain---begin");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(dispatch_get_main_queue(), ^{
        // 追加任務(wù)1
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    dispatch_sync(queue, ^{
        // 追加任務(wù)2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    dispatch_sync(queue, ^{
        // 追加任務(wù)3
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    NSLog(@"syncMain---end");
}

運行時會直接崩潰。
崩潰原因:這是因為我們在主線程中執(zhí)行 syncMain 方法,相當于把 syncMain 任務(wù)放到了主線程的主隊列中。而同步執(zhí)行會等待當前隊列中的任務(wù)執(zhí)行完畢,才會接著執(zhí)行。那么當我們把任務(wù)1追加到主隊列中,任務(wù)1就在等待主線程處理完 syncMain 任務(wù)。而 syncMain 任務(wù)需要等待任務(wù)1執(zhí)行完畢,才能接著執(zhí)行。
那么,現(xiàn)在的情況就是syncMain任務(wù)和任務(wù)1都在等對方執(zhí)行完畢。這樣大家互相等待,所以就卡住了,所以我們的任務(wù)執(zhí)行不了。
而之前的同步串行和同步并發(fā)都可以執(zhí)行不會崩潰的原因在于,他們雖然都在主線程中,但是并不在主隊列(main_queue)中,而是在自定義的隊列中。

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

代碼:

- (void)asyncMain {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印當前線程
    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];// 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    dispatch_async(queue, ^{
        // 追加任務(wù)2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    dispatch_async(queue, ^{
        // 追加任務(wù)3
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    NSLog(@"asyncMain---end");
}

log:

2018-04-08 23:14:51.902008+0800 應(yīng)用加載時間的優(yōu)化[6448:1043174] currentThread---<NSThread: 0x600000076a00>{number = 1, name = main}
2018-04-08 23:14:51.902183+0800 應(yīng)用加載時間的優(yōu)化[6448:1043174] asyncMain---begin
2018-04-08 23:14:51.902511+0800 應(yīng)用加載時間的優(yōu)化[6448:1043174] asyncMain---end
2018-04-08 23:14:53.906967+0800 應(yīng)用加載時間的優(yōu)化[6448:1043174] 1---<NSThread: 0x600000076a00>{number = 1, name = main}
2018-04-08 23:14:55.907579+0800 應(yīng)用加載時間的優(yōu)化[6448:1043174] 1---<NSThread: 0x600000076a00>{number = 1, name = main}
2018-04-08 23:14:57.909015+0800 應(yīng)用加載時間的優(yōu)化[6448:1043174] 2---<NSThread: 0x600000076a00>{number = 1, name = main}
2018-04-08 23:14:59.909606+0800 應(yīng)用加載時間的優(yōu)化[6448:1043174] 2---<NSThread: 0x600000076a00>{number = 1, name = main}
2018-04-08 23:15:01.910556+0800 應(yīng)用加載時間的優(yōu)化[6448:1043174] 3---<NSThread: 0x600000076a00>{number = 1, name = main}
2018-04-08 23:15:03.912148+0800 應(yīng)用加載時間的優(yōu)化[6448:1043174] 3---<NSThread: 0x600000076a00>{number = 1, name = main}

總結(jié):類似于異步執(zhí)行串行隊列,都是在第一時間執(zhí)行完syncConcurrent---beginsyncConcurrent---end后按順序間隔兩秒執(zhí)行任務(wù)。不同之處在于沒有開辟新的線程,而是在主線程中異步執(zhí)行。
所以參照上面所說的異步與并發(fā)的關(guān)系,實際上異步使自定義的任務(wù)延后執(zhí)行,整個線程仍是在主隊列中串行執(zhí)行自定義任務(wù)。而其效果,與異步串行隊列效果一致,都是異步串行,根本區(qū)別在于前者將任務(wù)放在主線程的主隊列中,而后者將任務(wù)在子線程的自定義隊列中,也就是前者只有一個線程,后者多開辟了一個線程。

線程間通信

在iOS開發(fā)過程中,我們一般在主線程里邊進行UI刷新,例如:點擊、滾動、拖拽等事件。我們通常把一些耗時的操作放在其他線程,比如說圖片下載、文件上傳等耗時操作。而當我們有時候在其他線程完成了耗時操作時,需要回到主線程,那么就用到了線程之間的通訊。
使用dispatch_async(dispatch_get_main_queue(), ^{ });回到主隊列。
代碼:

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

log:

2018-04-08 23:37:32.260716+0800 應(yīng)用加載時間的優(yōu)化[6512:1064267] 1---<NSThread: 0x6040004613c0>{number = 3, name = (null)}
2018-04-08 23:37:34.263732+0800 應(yīng)用加載時間的優(yōu)化[6512:1064267] 1---<NSThread: 0x6040004613c0>{number = 3, name = (null)}
2018-04-08 23:37:36.265455+0800 應(yīng)用加載時間的優(yōu)化[6512:1064179] 2---<NSThread: 0x600000078780>{number = 1, name = main}

總結(jié):可以看到在其他線程中先執(zhí)行任務(wù),執(zhí)行完了之后回到主線程執(zhí)行主線程的相應(yīng)操作。

GCD的其他常用方法

柵欄方法:dispatch_barrier_async

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

- (void)barrier {
    NSLog(@"barrier---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:2];// 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    dispatch_async(queue, ^{
        // 追加任務(wù)2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    dispatch_barrier_async(queue, ^{
        // 追加任務(wù) barrier
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模擬耗時操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    dispatch_async(queue, ^{
        // 追加任務(wù)3
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    dispatch_async(queue, ^{
        // 追加任務(wù)4
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模擬耗時操作
            NSLog(@"4---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    NSLog(@"barrier---end");
}

log:

2018-04-10 18:52:30.142820+0800 應(yīng)用加載時間的優(yōu)化[7378:1317650] barrier---begin
2018-04-10 18:52:30.143018+0800 應(yīng)用加載時間的優(yōu)化[7378:1317650] barrier---end
2018-04-10 18:52:32.147032+0800 應(yīng)用加載時間的優(yōu)化[7378:1317775] 1---<NSThread: 0x604000075880>{number = 4, name = (null)}
2018-04-10 18:52:32.147034+0800 應(yīng)用加載時間的優(yōu)化[7378:1317777] 2---<NSThread: 0x600000274500>{number = 3, name = (null)}
2018-04-10 18:52:34.148931+0800 應(yīng)用加載時間的優(yōu)化[7378:1317777] 2---<NSThread: 0x600000274500>{number = 3, name = (null)}
2018-04-10 18:52:34.148931+0800 應(yīng)用加載時間的優(yōu)化[7378:1317775] 1---<NSThread: 0x604000075880>{number = 4, name = (null)}
2018-04-10 18:52:36.149834+0800 應(yīng)用加載時間的優(yōu)化[7378:1317775] barrier---<NSThread: 0x604000075880>{number = 4, name = (null)}
2018-04-10 18:52:38.151610+0800 應(yīng)用加載時間的優(yōu)化[7378:1317775] barrier---<NSThread: 0x604000075880>{number = 4, name = (null)}
2018-04-10 18:52:40.153601+0800 應(yīng)用加載時間的優(yōu)化[7378:1317775] 3---<NSThread: 0x604000075880>{number = 4, name = (null)}
2018-04-10 18:52:40.153601+0800 應(yīng)用加載時間的優(yōu)化[7378:1317777] 4---<NSThread: 0x600000274500>{number = 3, name = (null)}
2018-04-10 18:52:42.155339+0800 應(yīng)用加載時間的優(yōu)化[7378:1317775] 3---<NSThread: 0x604000075880>{number = 4, name = (null)}
2018-04-10 18:52:42.155339+0800 應(yīng)用加載時間的優(yōu)化[7378:1317777] 4---<NSThread: 0x600000274500>{number = 3, name = (null)}

總結(jié):由于是異步,所有任務(wù)都異步執(zhí)行。加入了柵欄,所有異步任務(wù)先執(zhí)行柵欄前,再執(zhí)行柵欄,最后執(zhí)行柵欄后。

柵欄方法的應(yīng)用

使用柵欄方法實現(xiàn)多讀單寫。
比如在內(nèi)存中維護一份數(shù)據(jù),有很多地方可能會同時操作這塊數(shù)據(jù),如何保證數(shù)據(jù)安全??梢杂脰艡诜椒▉韺崿F(xiàn)。主要需要滿足:

  • 讀寫互斥
  • 寫寫互斥
  • 讀讀并發(fā)

@interface Person()

@property (nonatomic, strong) dispatch_queue_t concurrentQueue;
@property (nonatomic, strong) NSMutableDictionary *dic;

@end

@implementation Person

- (instancetype)init {
    self = [super init];
    if (self) {
        //單獨自定義一個并發(fā)隊列。
        _concurrentQueue = dispatch_queue_create("com.Person.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
        _dic = [NSMutableDictionary dictionary];
    }
    return self;
}

- (void)kc_setSafeObject:(id)object forKey:(NSString *)key {
    //防止傳進來的key改變
    key = [key copy];
    //異步寫數(shù)據(jù)。加入柵欄方法保證之前的執(zhí)行完,且內(nèi)部執(zhí)行完成之前,不會執(zhí)行其他操作(寫寫互斥)。
    dispatch_barrier_async(_concurrentQueue, ^{
        [_dic setObject:object forKey:key];
    });
}

- (id)kc_safeObjectForKey:(NSString *)key {
    __block NSString *temp;
    //同步讀數(shù)據(jù)。直接讀取數(shù)據(jù)。(這里不能使用異步,同時訪問name,age可能會導(dǎo)致混亂。因此可以允許多個任務(wù)同時進來,但是同步返回。讀讀并發(fā))
    dispatch_sync(_concurrentQueue, ^{
        temp = [_dic objectForKey:key];
    });
    return temp;
}

@end
延時方法:dispatch_after

在指定時間(例如3秒)之后執(zhí)行某個任務(wù)??梢杂?GCD 的 dispatch_after 函數(shù)來實現(xiàn)。
需要注意的是:dispatch_after 函數(shù)并不是在指定時間之后才開始執(zhí)行處理,而是在指定時間之后將任務(wù)追加到主隊列中。嚴格來說,這個時間并不是絕對準確的,但想要大致延遲執(zhí)行任務(wù),dispatch_after 函數(shù)是很有效的。
代碼:

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

log:

2018-04-10 19:08:03.125108+0800 應(yīng)用加載時間的優(yōu)化[7439:1332291] currentThread---<NSThread: 0x600000077e40>{number = 1, name = main}
2018-04-10 19:08:03.125318+0800 應(yīng)用加載時間的優(yōu)化[7439:1332291] after---begin
2018-04-10 19:08:03.125458+0800 應(yīng)用加載時間的優(yōu)化[7439:1332291] after---end
2018-04-10 19:08:05.125544+0800 應(yīng)用加載時間的優(yōu)化[7439:1332291] after---<NSThread: 0x600000077e40>{number = 1, name = main}

總結(jié):可以看到是異步執(zhí)行,沒有卡主線程。延遲兩秒后執(zhí)行 block 內(nèi)任務(wù)。

只執(zhí)行一次代碼:dispatch_once

我們在創(chuàng)建單例、或者有整個程序運行過程中只執(zhí)行一次的代碼時,我們就用到了 GCD 的 dispatch_once 函數(shù)。使用 dispatch_once 函數(shù)能保證某段代碼在程序運行過程中只被執(zhí)行1次,并且即使在多線程的環(huán)境下,dispatch_once 也可以保證線程安全。
代碼:

+ (instancetype)instance
{

    static CPReachability *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      instance = [[self alloc] init];
      instance.netType = CPAPPNetTypeWIFI;
      instance.wifiName = [instance GetWifiName];
    });
    return instance;
}

總結(jié):多用于單例。dispatch_once_t 其實是 long 類型,取其地址作為唯一標識符保證 block 內(nèi)部任務(wù)執(zhí)行且僅被執(zhí)行一次。

快速迭代方法:dispatch_apply

通常我們會用 for 循環(huán)遍歷,但是 GCD 給我們提供了快速迭代的函數(shù) dispatch_apply 。dispatch_apply 按照指定的次數(shù)將指定的任務(wù)追加到指定的隊列中,并等待全部隊列執(zhí)行結(jié)束。
我們可以利用異步隊列同時遍歷。比如說遍歷 0~5 這6個數(shù)字,for 循環(huán)的做法是每次取出一個元素,逐個遍歷。dispatch_apply 可以同時遍歷多個數(shù)字。
代碼:

- (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) {
        [NSThread sleepForTimeInterval:2];//模擬耗時操作
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
}

log:

2018-04-10 19:18:38.640868+0800 應(yīng)用加載時間的優(yōu)化[7506:1345125] apply---begin
2018-04-10 19:18:40.642368+0800 應(yīng)用加載時間的優(yōu)化[7506:1345125] 0---<NSThread: 0x6000000714c0>{number = 1, name = main}
2018-04-10 19:18:40.646225+0800 應(yīng)用加載時間的優(yōu)化[7506:1345197] 3---<NSThread: 0x600000265e80>{number = 5, name = (null)}
2018-04-10 19:18:40.646225+0800 應(yīng)用加載時間的優(yōu)化[7506:1345215] 1---<NSThread: 0x60400046c2c0>{number = 4, name = (null)}
2018-04-10 19:18:40.646272+0800 應(yīng)用加載時間的優(yōu)化[7506:1345198] 2---<NSThread: 0x600000265800>{number = 3, name = (null)}
2018-04-10 19:18:42.643982+0800 應(yīng)用加載時間的優(yōu)化[7506:1345125] 4---<NSThread: 0x6000000714c0>{number = 1, name = main}
2018-04-10 19:18:42.647137+0800 應(yīng)用加載時間的優(yōu)化[7506:1345197] 5---<NSThread: 0x600000265e80>{number = 5, name = (null)}
2018-04-10 19:18:42.647443+0800 應(yīng)用加載時間的優(yōu)化[7506:1345125] apply---end

總結(jié):首先我們能看到 dispatch_apply 是并發(fā)執(zhí)行的,因為在 queue 中,所以不能保證執(zhí)行順序。但是結(jié)果是同步的,會等待所有任務(wù)結(jié)束后繼續(xù)線程,最后執(zhí)行 apply---end。優(yōu)點在于大規(guī)模循環(huán)時比起 for 效率更高,比起手動開線程能防止線程數(shù)過多導(dǎo)致線程爆炸。
應(yīng)用場景:
如果我們從服務(wù)器獲取一個數(shù)組的數(shù)據(jù),那么我們可以使用該方法從而快速的批量字典轉(zhuǎn)模型。

dispatch_group

dispatch_group 是 GCD 中的一組方法,他有一個組的概念,可以把相關(guān)的任務(wù)歸并到一個組內(nèi)來執(zhí)行,通過監(jiān)聽組內(nèi)所有任務(wù)的執(zhí)行情況來做相應(yīng)處理。

dispatch_group_create

用于創(chuàng)建任務(wù)組
dispatch_group_t dispatch_group_create(void);

dispatch_group_async

把異步任務(wù)提交到指定任務(wù)組和指定下拿出隊列執(zhí)行

void dispatch_group_async(dispatch_group_t group,
                          dispatch_queue_t queue,
                          dispatch_block_t block);
  • group ——對應(yīng)的任務(wù)組,之后可以通過dispatch_group_wait或者dispatch_group_notify監(jiān)聽任務(wù)組內(nèi)任務(wù)的執(zhí)行情況
  • queue ——block任務(wù)執(zhí)行的線程隊列,任務(wù)組內(nèi)不同任務(wù)的隊列可以不同
  • block —— 執(zhí)行任務(wù)的block
dispatch_group_notify

待任務(wù)組執(zhí)行完畢時調(diào)用,不會阻塞當前線程
代碼:

- (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), ^{
        // 追加任務(wù)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), ^{
        // 追加任務(wù)2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    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];// 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);// 打印當前線程
        }
        NSLog(@"group---end");
    });
}

log:

2018-04-10 19:31:53.446408+0800 應(yīng)用加載時間的優(yōu)化[7570:1360370] currentThread---<NSThread: 0x600000075ac0>{number = 1, name = main}
2018-04-10 19:31:53.446553+0800 應(yīng)用加載時間的優(yōu)化[7570:1360370] group---begin
2018-04-10 19:31:55.450356+0800 應(yīng)用加載時間的優(yōu)化[7570:1360470] 1---<NSThread: 0x600000272640>{number = 3, name = (null)}
2018-04-10 19:31:55.450352+0800 應(yīng)用加載時間的優(yōu)化[7570:1360463] 2---<NSThread: 0x600000273c00>{number = 4, name = (null)}
2018-04-10 19:31:57.450882+0800 應(yīng)用加載時間的優(yōu)化[7570:1360470] 1---<NSThread: 0x600000272640>{number = 3, name = (null)}
2018-04-10 19:31:57.450882+0800 應(yīng)用加載時間的優(yōu)化[7570:1360463] 2---<NSThread: 0x600000273c00>{number = 4, name = (null)}
2018-04-10 19:31:59.452501+0800 應(yīng)用加載時間的優(yōu)化[7570:1360370] 3---<NSThread: 0x600000075ac0>{number = 1, name = main}
2018-04-10 19:32:01.454278+0800 應(yīng)用加載時間的優(yōu)化[7570:1360370] 3---<NSThread: 0x600000075ac0>{number = 1, name = main}
2018-04-10 19:32:01.454655+0800 應(yīng)用加載時間的優(yōu)化[7570:1360370] group---end

總結(jié):異步并發(fā)執(zhí)行組一內(nèi)任務(wù)和組二內(nèi)任務(wù),使用 dispatch_group_notify 監(jiān)測以上兩任務(wù)完成后回主線程進入 block 內(nèi)執(zhí)行任務(wù)三。全程異步,不卡線程。

dispatch_group_wait

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

- (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), ^{
        // 追加任務(wù)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), ^{
        // 追加任務(wù)2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    // 等待上面的任務(wù)全部完成后,會往下繼續(xù)執(zhí)行(會阻塞當前線程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group---end");
}

log:

2018-04-10 19:49:33.996536+0800 應(yīng)用加載時間的優(yōu)化[7766:1383789] currentThread---<NSThread: 0x604000067f40>{number = 1, name = main}
2018-04-10 19:49:33.996684+0800 應(yīng)用加載時間的優(yōu)化[7766:1383789] group---begin
2018-04-10 19:49:36.000831+0800 應(yīng)用加載時間的優(yōu)化[7766:1383883] 2---<NSThread: 0x60400026ff00>{number = 3, name = (null)}
2018-04-10 19:49:36.000831+0800 應(yīng)用加載時間的優(yōu)化[7766:1383886] 1---<NSThread: 0x60000026ffc0>{number = 4, name = (null)}
2018-04-10 19:49:38.001697+0800 應(yīng)用加載時間的優(yōu)化[7766:1383883] 2---<NSThread: 0x60400026ff00>{number = 3, name = (null)}
2018-04-10 19:49:38.001987+0800 應(yīng)用加載時間的優(yōu)化[7766:1383886] 1---<NSThread: 0x60000026ffc0>{number = 4, name = (null)}
2018-04-10 19:49:38.003862+0800 應(yīng)用加載時間的優(yōu)化[7766:1383789] group---end

總結(jié):使用 dispatch_group_wait(group, DISPATCH_TIME_FOREVER);卡線程來等待 group 內(nèi)容全部執(zhí)行完之后繼續(xù)下面任務(wù),實現(xiàn)同步。如果去掉 dispatch_group_wait則不卡線程,第一時間執(zhí)行 group---end,然后執(zhí)行 group 中內(nèi)容。

dispatch_group_enter、dispatch_group_leave

void dispatch_group_enter(dispatch_group_t group);
用于添加對應(yīng)任務(wù)組中的未執(zhí)行完畢的任務(wù)數(shù),執(zhí)行一次,未執(zhí)行完畢的任務(wù)數(shù)加1,當未執(zhí)行完畢任務(wù)數(shù)為0的時候,才會使 dispatch_group_wait 解除阻塞和dispatch_group_notify 的 block執(zhí)行。
void dispatch_group_leave(dispatch_group_t group);
用于減少任務(wù)組中的未執(zhí)行完畢的任務(wù)數(shù),執(zhí)行一次,未執(zhí)行完畢的任務(wù)數(shù)減1,dispatch_group_enter 和 dispatch_group_leave 要匹配,不然系統(tǒng)會認為 group 任務(wù)沒有執(zhí)行完畢。
代碼:

- (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, ^{
        // 追加任務(wù)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, ^{
        // 追加任務(wù)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");
    });//
    // 等待上面的任務(wù)全部完成后,會往下繼續(xù)執(zhí)行(會阻塞當前線程)
    //    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);//
    NSLog(@"group---end"); 
}

log:

2018-04-10 20:05:44.440633+0800 應(yīng)用加載時間的優(yōu)化[7796:1394822] currentThread---<NSThread: 0x60000007f000>{number = 1, name = main}
2018-04-10 20:05:44.440802+0800 應(yīng)用加載時間的優(yōu)化[7796:1394822] group---begin
2018-04-10 20:05:44.441216+0800 應(yīng)用加載時間的優(yōu)化[7796:1394822] group---end
2018-04-10 20:05:46.443538+0800 應(yīng)用加載時間的優(yōu)化[7796:1394930] 2---<NSThread: 0x600000474440>{number = 3, name = (null)}
2018-04-10 20:05:46.443549+0800 應(yīng)用加載時間的優(yōu)化[7796:1394928] 1---<NSThread: 0x60400026a580>{number = 4, name = (null)}
2018-04-10 20:05:48.445104+0800 應(yīng)用加載時間的優(yōu)化[7796:1394930] 2---<NSThread: 0x600000474440>{number = 3, name = (null)}
2018-04-10 20:05:48.445101+0800 應(yīng)用加載時間的優(yōu)化[7796:1394928] 1---<NSThread: 0x60400026a580>{number = 4, name = (null)}
2018-04-10 20:05:50.445690+0800 應(yīng)用加載時間的優(yōu)化[7796:1394822] 3---<NSThread: 0x60000007f000>{number = 1, name = main}
2018-04-10 20:05:52.447481+0800 應(yīng)用加載時間的優(yōu)化[7796:1394822] 3---<NSThread: 0x60000007f000>{number = 1, name = main}
2018-04-10 20:05:52.447792+0800 應(yīng)用加載時間的優(yōu)化[7796:1394822] group---end

總結(jié):等同于 dispatch_group_async。

信號量:dispatch_semaphore

GCD 中的信號量是指 Dispatch Semaphore,是持有計數(shù)的信號。類似于過高速路收費站的欄桿??梢酝ㄟ^時,打開欄桿,不可以通過時,關(guān)閉欄桿。在Dispatch Semaphore中,使用計數(shù)來完成這個功能,計數(shù)為0時等待,不可通過。計數(shù)為1或大于1時,計數(shù)減1且不等待,可通過。

Dispatch Semaphore提供了三個函數(shù)。

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

注意:信號量的使用前提是:想清楚你需要處理哪個線程等待(阻塞),又要哪個線程繼續(xù)執(zhí)行,然后使用信號量。

Dispatch Semaphore 在實際開發(fā)中主要用于:
1.保持線程同步,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)
2.保證線程安全,為線程加鎖

Dispatch Semaphore 線程同步

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

- (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, ^{
        // 追加任務(wù)1
        [NSThread sleepForTimeInterval:2];// 模擬耗時操作
        NSLog(@"1---%@",[NSThread currentThread]);// 打印當前線程
        number = 100;
        dispatch_semaphore_signal(semaphore);
    });
    NSLog(@"main task continue");
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %zd",number);
}

log:

2018-04-10 21:58:57.974515+0800 應(yīng)用加載時間的優(yōu)化[8026:1443631] currentThread---<NSThread: 0x600000073900>{number = 1, name = main}
2018-04-10 21:58:57.974641+0800 應(yīng)用加載時間的優(yōu)化[8026:1443631] semaphore---begin
2018-04-10 21:58:57.974790+0800 應(yīng)用加載時間的優(yōu)化[8026:1443631] main task continue
2018-04-10 21:58:59.978823+0800 應(yīng)用加載時間的優(yōu)化[8026:1443709] 1---<NSThread: 0x604000269240>{number = 3, name = (null)}
2018-04-10 21:58:59.979016+0800 應(yīng)用加載時間的優(yōu)化[8026:1443631] semaphore---end,number = 100

總結(jié):使用 semaphore 實現(xiàn)線程同步??梢栽谔砑?dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 之前實現(xiàn)主線程異步并發(fā)操作,在之后進行線程同步,等待子線程任務(wù)結(jié)束后執(zhí)行后續(xù)操作。

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

線程安全:
如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結(jié)果和單線程運行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。
若每個線程中對全局變量、靜態(tài)變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執(zhí)行寫操作(更改變量),一般都需要考慮線程同步,否則的話就可能影響線程安全。

線程同步:
可理解為線程 A 和 線程 B 一塊配合,A 執(zhí)行到一定程度時要依靠線程 B 的某個結(jié)果,于是停下來,示意 B 運行;B 依言執(zhí)行,再將結(jié)果給 A;A 再繼續(xù)操作。
舉個簡單例子就是:兩個人在一起聊天。兩個人不能同時說話,避免聽不清(操作沖突)。等一個人說完(一個線程結(jié)束操作),另一個再說(另一個線程再開始操作)。

以售票為例:
非線程安全:(不使用 semaphore):
代碼:

- (void)initTicketStatusNotSafe {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印當前線程
    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;
        }
    }
}

Log:

2018-04-10 22:25:01.047999+0800 應(yīng)用加載時間的優(yōu)化[8218:1472786] currentThread---<NSThread: 0x604000064880>{number = 1, name = main}
2018-04-10 22:25:01.048150+0800 應(yīng)用加載時間的優(yōu)化[8218:1472786] semaphore---begin
2018-04-10 22:25:01.048378+0800 應(yīng)用加載時間的優(yōu)化[8218:1472878] 剩余票數(shù):48 窗口:<NSThread: 0x6000002651c0>{number = 4, name = (null)}
2018-04-10 22:25:01.048387+0800 應(yīng)用加載時間的優(yōu)化[8218:1472892] 剩余票數(shù):49 窗口:<NSThread: 0x604000272800>{number = 3, name = (null)}
2018-04-10 22:25:01.249347+0800 應(yīng)用加載時間的優(yōu)化[8218:1472878] 剩余票數(shù):47 窗口:<NSThread: 0x6000002651c0>{number = 4, name = (null)}
2018-04-10 22:25:01.249354+0800 應(yīng)用加載時間的優(yōu)化[8218:1472892] 剩余票數(shù):46 窗口:<NSThread: 0x604000272800>{number = 3, name = (null)}
2018-04-10 22:25:01.450904+0800 應(yīng)用加載時間的優(yōu)化[8218:1472878] 剩余票數(shù):45 窗口:<NSThread: 0x6000002651c0>{number = 4, name = (null)}
2018-04-10 22:25:01.450904+0800 應(yīng)用加載時間的優(yōu)化[8218:1472892] 剩余票數(shù):45 窗口:<NSThread: 0x604000272800>{number = 3, name = (null)}
2018-04-10 22:25:01.651358+0800 應(yīng)用加載時間的優(yōu)化[8218:1472892] 剩余票數(shù):44 窗口:<NSThread: 0x604000272800>{number = 3, name = (null)}
2018-04-10 22:25:01.651358+0800 應(yīng)用加載時間的優(yōu)化[8218:1472878] 剩余票數(shù):44 窗口:<NSThread: 0x6000002651c0>{number = 4, name = (null)}
.
.
.
2018-04-10 22:25:07.753135+0800 應(yīng)用加載時間的優(yōu)化
[8218:1472878] 剩余票數(shù):2 窗口:<NSThread: 0x6000002651c0>{number = 4, name = (null)}
2018-04-10 22:25:07.753135+0800 應(yīng)用加載時間的優(yōu)化[8218:1472892] 剩余票數(shù):2 窗口:<NSThread: 0x604000272800>{number = 3, name = (null)}
2018-04-10 22:25:07.958573+0800 應(yīng)用加載時間的優(yōu)化[8218:1472878] 剩余票數(shù):1 窗口:<NSThread: 0x6000002651c0>{number = 4, name = (null)}
2018-04-10 22:25:07.958574+0800 應(yīng)用加載時間的優(yōu)化[8218:1472892] 剩余票數(shù):0 窗口:<NSThread: 0x604000272800>{number = 3, name = (null)}

總結(jié):剩余票數(shù)錯亂,且重復(fù),存在多個線程同時訪問的情況,也就說存在同一張票賣兩次的情況。

線程安全:(使用 semaphore 加鎖):
使用 semaphoreLock = dispatch_semaphore_create(1); 創(chuàng)建鎖
使用 dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER); 加鎖
使用 dispatch_semaphore_signal(semaphoreLock); 解鎖
用加鎖和解鎖包含住可能會被多線程同時執(zhí)行修改的代碼。
代碼:

static dispatch_semaphore_t semaphoreLock;

- (void)initTicketStatusSafe {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印當前線程
    NSLog(@"semaphore---begin");
    //value表示最大線程同時訪問數(shù),此處為1,更改會出問題
    semaphoreLock = dispatch_semaphore_create(1);
    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 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{
            //如果已賣完,關(guān)閉售票窗口
            NSLog(@"所有火車票均已售完");
            // 相當于解鎖
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }// 相當于解鎖
        dispatch_semaphore_signal(semaphoreLock);
    }
}

log:

2018-04-10 22:41:43.466847+0800 應(yīng)用加載時間的優(yōu)化[8424:1495856] currentThread---<NSThread: 0x60000007b700>{number = 1, name = main}
2018-04-10 22:41:43.466991+0800 應(yīng)用加載時間的優(yōu)化[8424:1495856] semaphore---begin
2018-04-10 22:41:43.467215+0800 應(yīng)用加載時間的優(yōu)化[8424:1495959] 剩余票數(shù):49 窗口:<NSThread: 0x60400046e840>{number = 3, name = (null)}
2018-04-10 22:41:43.671865+0800 應(yīng)用加載時間的優(yōu)化[8424:1495961] 剩余票數(shù):48 窗口:<NSThread: 0x600000270840>{number = 4, name = (null)}
2018-04-10 22:41:43.874748+0800 應(yīng)用加載時間的優(yōu)化[8424:1495959] 剩余票數(shù):47 窗口:<NSThread: 0x60400046e840>{number = 3, name = (null)}
2018-04-10 22:41:44.078812+0800 應(yīng)用加載時間的優(yōu)化[8424:1495961] 剩余票數(shù):46 窗口:<NSThread: 0x600000270840>{number = 4, name = (null)}
2018-04-10 22:41:44.280894+0800 應(yīng)用加載時間的優(yōu)化[8424:1495959] 剩余票數(shù):45 窗口:<NSThread: 0x60400046e840>{number = 3, name = (null)}
2018-04-10 22:41:44.483298+0800 應(yīng)用加載時間的優(yōu)化[8424:1495961] 剩余票數(shù):44 窗口:<NSThread: 0x600000270840>{number = 4, name = (null)}
2018-04-10 22:41:44.686228+0800 應(yīng)用加載時間的優(yōu)化[8424:1495959] 剩余票數(shù):43 窗口:<NSThread: 0x60400046e840>{number = 3, name = (null)}
2018-04-10 22:41:44.890615+0800 應(yīng)用加載時間的優(yōu)化[8424:1495961] 剩余票數(shù):42 窗口:<NSThread: 0x600000270840>{number = 4, name = (null)}
.
.
.
2018-04-10 22:41:53.450842+0800 應(yīng)用加載時間的優(yōu)化[8424:1495961] 剩余票數(shù):0 窗口:<NSThread: 0x600000270840>{number = 4, name = (null)}
2018-04-10 22:41:53.656188+0800 應(yīng)用加載時間的優(yōu)化[8424:1495959] 所有火車票均已售完
2018-04-10 22:41:53.656569+0800 應(yīng)用加載時間的優(yōu)化[8424:1495961] 所有火車票均已售完

總結(jié):可以看出,在考慮了線程安全的情況下,使用 dispatch_semaphore 機制之后,得到的票數(shù)是正確的,沒有出現(xiàn)混亂的情況。我們也就解決了多個線程同步的問題。這也就是線程鎖的重要性。

死鎖

使用dispatch_semaphore給線程加鎖可以解決數(shù)據(jù)同步問題,但是同時要注意造成死鎖導(dǎo)致應(yīng)用崩潰。
例如:某方法中,等待服務(wù)器接口請求數(shù)據(jù)成功后,返回數(shù)據(jù),實現(xiàn)異步網(wǎng)絡(luò)請求的同步實現(xiàn)。

-(void)requestA{
    
    //創(chuàng)建信號量并設(shè)置計數(shù)默認為0
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    
    [AFNmanager GET:@"" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {        
        dispatch_semaphore_signal(sema);

    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        //計數(shù)+1操作        
        dispatch_semaphore_signal(sema);
        
    }];

    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

}

注意:該方法可能會造成死鎖。首先,該方法在主線程中加鎖,阻塞主線程,等待子線程完成網(wǎng)絡(luò)請求任務(wù)后,發(fā)出解鎖信號。解除主線程鎖,繼續(xù)運行。
但是在AFNetworking在完成請求的block中,AF內(nèi)部的completion會回到主線程進行回調(diào)。而主線程加鎖已經(jīng)阻塞了主線程,因此無法發(fā)出解鎖信號,造成死鎖。
對于AFNetworking中,有completionQueue屬性,可以設(shè)置請求完成的回調(diào)線程,設(shè)置成子線程,即可解決死鎖問題。
對于其他網(wǎng)絡(luò)框架,與dispatch_semaphore結(jié)合使用時,均需要注意不要造成死鎖。

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

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

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