iOS原理 多線程2 -- GCD詳解

iOS原理 文章匯總

一餐胀、GCD簡介

GCD全稱是Grand Central Dispatch,是Apple開發(fā)中用于實現(xiàn)多線程編程的一種解決方案。GCD是純C語言,提供了很多強(qiáng)大的函數(shù)。

GCD的優(yōu)勢如下:

  • GCD 可用于多核的并行運(yùn)算囤耳;
  • GCD 會自動利用更多的CPU內(nèi)核(比如雙核、四核)偶芍;
  • GCD 會自動管理線程的生命周期(創(chuàng)建線程充择、調(diào)度任務(wù)、銷毀線程)匪蟀;
  • 代碼簡潔椎麦,不需要編寫任何線程管理的代碼。

二材彪、GCD的任務(wù)和隊列

GCD中有兩個核心概念:『任務(wù)』和『隊列』观挎。

1.任務(wù)

任務(wù)即執(zhí)行操作琴儿,簡單來說是在線程上執(zhí)行的那段代碼,在GCD中被封裝在block中键兜。任務(wù)的執(zhí)行方式有兩種:『同步執(zhí)行』凤类,『異步執(zhí)行』。

同步執(zhí)行sync

  • 不會開啟新線程普气,只在當(dāng)前線程中串行執(zhí)行任務(wù)谜疤,需要等待。
  • 對于代碼執(zhí)行來說现诀,必須等待當(dāng)前語句執(zhí)?完畢夷磕,才會執(zhí)?下?條語句。

異步執(zhí)行async

  • 會開啟新線程仔沿,并發(fā)執(zhí)行任務(wù)坐桩,不需等待。
  • 對于代碼執(zhí)行來說封锉,不?等待當(dāng)前語句執(zhí)?完畢绵跷,就可以執(zhí)?下?條語句。
2.隊列(Dispatch Queue)

這里的隊列指執(zhí)行任務(wù)的等待隊列成福,即用來存放任務(wù)的隊列碾局。隊列是一種特殊的線性表,采用FIFO(先進(jìn)先出)的原則奴艾,即最早被添加的任務(wù)會先被調(diào)度執(zhí)行净当。每執(zhí)行一個任務(wù),就從隊列中釋放一個任務(wù)蕴潦。隊列的結(jié)構(gòu)可參考下圖:

GCD中的隊列也分為兩種:『串行隊列』像啼,『并發(fā)隊列』。

  • 串行隊列Serial Dispatch Queue):只開啟一個線程潭苞,同一時間只執(zhí)行一個任務(wù)忽冻,一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù)萄传。
  • 并發(fā)隊列Concurrent Dispatch Queue):可以開啟多個線程甚颂,同一時間可以并發(fā)執(zhí)行多個任務(wù),不用等待秀菱。

注意:并發(fā)隊列中的任務(wù)只有在異步執(zhí)行下才會開啟多個線程振诬,同步執(zhí)行下只會在當(dāng)前線程中處理任務(wù)。

兩者的任務(wù)執(zhí)行情況如下圖所示:

串行隊列(Serial Dispatch Queue)
并發(fā)隊列(Concurrent Dispatch Queue)

三衍菱、GCD的使用步驟

GCD使用步驟教簡單赶么,只有兩步:
1. 創(chuàng)建一個隊列(串行隊列或者并行隊列)。
2. 創(chuàng)建任務(wù)脊串,將任務(wù)添加到隊列(創(chuàng)建時需指定是同步執(zhí)行還是異步執(zhí)行)辫呻。

1.隊列的創(chuàng)建和獲取
  • 創(chuàng)建串行隊列和并發(fā)隊列

    通過dispatch_queue_create函數(shù)可以創(chuàng)建串行隊列和并發(fā)隊列清钥,函數(shù)需要傳入兩個參數(shù):

    • 參數(shù)1:為隊列的標(biāo)識符,可以為空放闺;
    • 參數(shù)2:為隊列的類型祟昭,DISPATCH_QUEUE_SERIAL表示串行隊列,DISPATCH_QUEUE_CONCURRENT表示并發(fā)隊列怖侦。其中DISPATCH_QUEUE_SERIAL本質(zhì)是NULL篡悟。
    //創(chuàng)建一個串行隊列,參數(shù)2也可以直接傳NULL
    dispatch_queue_t sQueue = dispatch_queue_create("lab1", DISPATCH_QUEUE_SERIAL);
    //創(chuàng)建一個并發(fā)隊列
    dispatch_queue_t cQueue = dispatch_queue_create("lab2", DISPATCH_QUEUE_CONCURRENT);
    
    #define DISPATCH_QUEUE_SERIAL NULL  
    
  • 獲取主隊列(Main Dispatch Queue)

    主隊列是一種特殊的串行隊列匾寝,程序啟動時由系統(tǒng)默認(rèn)提供搬葬,所有任務(wù)均默認(rèn)添加在主隊列。主隊列中所有的任務(wù)艳悔,無論是同步執(zhí)行還是異步執(zhí)行急凰,都只會在主線程中執(zhí)行。通過dispatch_get_main_queue()函數(shù)來獲取主隊列:

    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
  • 獲取全局并發(fā)隊列(Global Dispatch Queue)

    在使?多線程開發(fā)時猜年,如果對隊列沒有特殊需求抡锈,在執(zhí)?異步任務(wù)時,可以直接使?全局并發(fā)隊列乔外。通過dispatch_get_global_queue函數(shù)來獲取全局并發(fā)隊列企孩,需要傳入兩個參數(shù):

    • 參數(shù)1:表示隊列優(yōu)先級,一般傳入 DISPATCH_QUEUE_PRIORITY_DEFAULT袁稽。
    • 參數(shù)2:暫時沒用,傳0即可擒抛。
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
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ù)代碼
});

四、對串行隊列的分析

串行隊列里的任務(wù)歧沪,會遵循FIFO規(guī)則依次調(diào)度執(zhí)行歹撒,由于任務(wù)的執(zhí)行方式分為同步執(zhí)行和異步執(zhí)行,所以串行隊列中的任務(wù)存在以下的情況:

  1. 全部都是同步執(zhí)行的任務(wù)
  2. 全部都是異步執(zhí)行的任務(wù)
  3. 既有同步執(zhí)行又有異步執(zhí)行的任務(wù)
  4. 線程死鎖

接下來通過具體示例來分析這幾種組合情況诊胞。

1. 全部都是同步執(zhí)行的任務(wù)

如下面代碼所示暖夭,往一個串行隊列中添加三個同步執(zhí)行任務(wù),然后打印執(zhí)行結(jié)果撵孤。

//串行隊列+同步執(zhí)行任務(wù)
-(void)testSerialAndSync{
    
    NSLog(@" ==== method thread = %@", [NSThread currentThread]);
    NSLog(@" ==== begin");
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
    //添加同步執(zhí)行任務(wù)1
    dispatch_sync(queue, ^{
        
        [NSThread sleepForTimeInterval:2];
        NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
    });
    //添加同步執(zhí)行任務(wù)2
    dispatch_sync(queue, ^{
        
        [NSThread sleepForTimeInterval:2];
        NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
    });
    //添加同步執(zhí)行任務(wù)3
    dispatch_sync(queue, ^{
        
        [NSThread sleepForTimeInterval:2];
        NSLog(@" ==== task3 thread = %@", [NSThread currentThread]);
    });
    
    NSLog(@" ==== end");
}

//打印結(jié)果
2020-11-15 01:16:07.499128+0800 GCDDemo[17257:70356957]  ==== method thread = <NSThread: 0x600001704100>{number = 1, name = main}
2020-11-15 01:16:07.499199+0800 GCDDemo[17257:70356957]  ==== begin
2020-11-15 01:16:09.500480+0800 GCDDemo[17257:70356957]  ==== task1 thread = <NSThread: 0x600001704100>{number = 1, name = main}
2020-11-15 01:16:11.501599+0800 GCDDemo[17257:70356957]  ==== task2 thread = <NSThread: 0x600001704100>{number = 1, name = main}
2020-11-15 01:16:13.502912+0800 GCDDemo[17257:70356957]  ==== task3 thread = <NSThread: 0x600001704100>{number = 1, name = main}
2020-11-15 01:16:13.503060+0800 GCDDemo[17257:70356957]  ==== end

從打印結(jié)果可知迈着,三個任務(wù)均在當(dāng)前線程(主線程)里按順序依次執(zhí)行。因此邪码,當(dāng)一個串行隊列里都是同步執(zhí)行的任務(wù)裕菠,不會開辟新線程,所有任務(wù)均在當(dāng)前線程同步執(zhí)行闭专。

2. 全部都是異步執(zhí)行的任務(wù)

如下面代碼所示奴潘,往一個串行隊列中添加三個異步執(zhí)行任務(wù)旧烧,然后打印執(zhí)行結(jié)果。

//串行隊列+異步執(zhí)行任務(wù)
-(void)testSerialAndAsync{
    
    NSLog(@" ==== method thread = %@", [NSThread currentThread]);
    NSLog(@" ==== begin");
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
    //添加異步執(zhí)行任務(wù)1
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:3];
        NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
    });
    //添加異步執(zhí)行任務(wù)2
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:2];
        NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
    });
    //添加異步執(zhí)行任務(wù)3
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:1];
        NSLog(@" ==== task3 thread = %@", [NSThread currentThread]);
    });
    
    NSLog(@" ==== end");
}

//打印結(jié)果
2020-11-16 10:12:25.065872+0800 GCDDemo[41263:73331602]  ==== method thread = <NSThread: 0x60000170c100>{number = 1, name = main}
2020-11-16 10:12:25.065953+0800 GCDDemo[41263:73331602]  ==== begin
2020-11-16 10:12:25.066017+0800 GCDDemo[41263:73331602]  ==== end
2020-11-16 10:12:28.071048+0800 GCDDemo[41263:73331984]  ==== task1 thread = <NSThread: 0x60000175c2c0>{number = 2, name = (null)}
2020-11-16 10:12:30.074008+0800 GCDDemo[41263:73331984]  ==== task2 thread = <NSThread: 0x60000175c2c0>{number = 2, name = (null)}
2020-11-16 10:12:31.076458+0800 GCDDemo[41263:73331984]  ==== task3 thread = <NSThread: 0x60000175c2c0>{number = 2, name = (null)}

從打印結(jié)果可知画髓,這個過程中只新開啟了一條子線程掘剪,三個任務(wù)在子線程中按序依次執(zhí)行。因此奈虾,當(dāng)一個串行隊列里全是異步執(zhí)行的任務(wù)夺谁,只會開啟一個新線程,所有任務(wù)在這個線程里按順序同步執(zhí)行愚墓。

3. 既有同步執(zhí)行又有異步執(zhí)行的任務(wù)

如下面代碼所示予权,往一個串行隊列中穿插添加兩個同步執(zhí)行的任務(wù)和兩個異步執(zhí)行的任務(wù)。

//串行隊列+兩種方式執(zhí)行的任務(wù)
-(void)testSerial{
    
    NSLog(@" ==== method thread = %@", [NSThread currentThread]);
    NSLog(@" ==== begin");
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
    //添加異步執(zhí)行任務(wù)1
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:4];
        NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
    });
    //添加同步執(zhí)行任務(wù)2
    dispatch_sync(queue, ^{
        
        [NSThread sleepForTimeInterval:3];
        NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
    });
    //添加異步執(zhí)行任務(wù)3
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:2];
        NSLog(@" ==== task3 thread = %@", [NSThread currentThread]);
    });
    //添加同步執(zhí)行任務(wù)4
    dispatch_sync(queue, ^{
        
        [NSThread sleepForTimeInterval:1];
        NSLog(@" ==== task4 thread = %@", [NSThread currentThread]);
    });
    
    NSLog(@" ==== end");
}

//打印結(jié)果
2020-11-16 14:25:03.502639+0800 GCDDemo[56034:75334852]  ==== method thread = <NSThread: 0x600001704140>{number = 1, name = main}
2020-11-16 14:25:03.502730+0800 GCDDemo[56034:75334852]  ==== begin
2020-11-16 14:25:07.503842+0800 GCDDemo[56034:75335420]  ==== task1 thread = <NSThread: 0x600001749a80>{number = 2, name = (null)}
2020-11-16 14:25:10.505354+0800 GCDDemo[56034:75334852]  ==== task2 thread = <NSThread: 0x600001704140>{number = 1, name = main}
2020-11-16 14:25:12.506879+0800 GCDDemo[56034:75335420]  ==== task3 thread = <NSThread: 0x600001749a80>{number = 2, name = (null)}
2020-11-16 14:25:13.508375+0800 GCDDemo[56034:75334852]  ==== task4 thread = <NSThread: 0x600001704140>{number = 1, name = main}
2020-11-16 14:25:13.508554+0800 GCDDemo[56034:75334852]  ==== end

從打印結(jié)果可知浪册,這個過程中新開啟了一條子線程扫腺,兩個異步任務(wù)在子線程執(zhí)行,兩個同步任務(wù)在當(dāng)前線程(主線程)中執(zhí)行村象,且這4個任務(wù)是按順序依次執(zhí)行笆环。因此,當(dāng)一個串行隊列中既有同步執(zhí)行任務(wù)厚者,還有異步執(zhí)行任務(wù)時躁劣,會新開啟一個子線程,所有異步任務(wù)均在子線程上執(zhí)行库菲,所有同步任務(wù)均在當(dāng)前線程中執(zhí)行账忘,雖然是在兩個線程上執(zhí)行,但所有任務(wù)依舊按序同步執(zhí)行熙宇。

4. 線程死鎖

當(dāng)往一個串行隊列中添加同步執(zhí)行任務(wù)時鳖擒,若使用不當(dāng),可能會導(dǎo)致線程死鎖烫止,這里對普通串行隊列和主隊列來分別進(jìn)行分析蒋荚。

  • 普通串行隊列死鎖
-(void)testSerialAndSyncLock{
    
    NSLog(@" ==== method thread = %@", [NSThread currentThread]);
    NSLog(@" ==== begin");
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
    //添加異步執(zhí)行任務(wù)1
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:2];
        NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
        //添加同步執(zhí)行任務(wù)2
        dispatch_sync(queue, ^{
            
            [NSThread sleepForTimeInterval:2];
            NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
        });
    });
    
    NSLog(@" ==== end");
}

//打印結(jié)果
2020-11-16 14:48:47.797169+0800 GCDDemo[57469:75514015]  ==== method thread = <NSThread: 0x600001708640>{number = 1, name = main}
2020-11-16 14:48:47.797242+0800 GCDDemo[57469:75514015]  ==== begin
2020-11-16 14:48:47.797304+0800 GCDDemo[57469:75514015]  ==== end
2020-11-16 14:48:49.802076+0800 GCDDemo[57469:75514581]  ==== task1 thread = <NSThread: 0x600001757dc0>{number = 2, name = (null)}

//報錯
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
dispatch_sync_f_slow

在這個例子中,先往串行隊列queue中添加一個異步執(zhí)行任務(wù)task1馆蠕,然后在task1里又往隊列中添加同步執(zhí)行任務(wù)task2期升。從前面的分析可知,task1會在子線程中執(zhí)行互躬,task2會在主線程中執(zhí)行播赁,但從打印結(jié)果可知,子線程死鎖了吨铸。

為什么會這樣呢行拢?從代碼邏輯可知,串行隊列queue里的任務(wù)情況如下圖所示:

由于是串行隊列,所以需要先執(zhí)行完task1舟奠,才能執(zhí)行task2竭缝。上文已說明,任務(wù)其實是寫在block里的代碼沼瘫,所以task1里具體包含sleepForTimeInterval抬纸、NSLogdispatch_sync這三個操作耿戚,而執(zhí)行dispatch_sync操作湿故,必須要先執(zhí)行其block里的任務(wù)(也就是task2),所以這里完成task1的執(zhí)行需要先等待task2執(zhí)行完膜蛔,而task2的執(zhí)行又必須等待task1的完成坛猪,這樣就造成了兩個任務(wù)互相等待的局面,導(dǎo)致串行隊列阻塞皂股,使得正在執(zhí)行任務(wù)的子線程死鎖墅茉。

  • 主隊列死鎖
-(void)testMainQueueAndSyncLock{
    
    NSLog(@" ==== method thread = %@", [NSThread currentThread]);
    NSLog(@" ==== begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    //添加同步執(zhí)行任務(wù)1
    dispatch_sync(queue, ^{
        
        [NSThread sleepForTimeInterval:2];
        NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
    });
    
    
    NSLog(@" ==== end");
}

//執(zhí)行結(jié)果
2020-11-16 15:10:25.390210+0800 GCDDemo[58693:75674415]  ==== method thread = <NSThread: 0x600001709040>{number = 1, name = main}
2020-11-16 15:10:25.390282+0800 GCDDemo[58693:75674415]  ==== begin

//報錯
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
dispatch_sync_f_slow

這里主線程死鎖的原因和上面一樣,雖然說task1里沒有添加其他同步執(zhí)行任務(wù)呜呐,但是外層方法testMainQueueAndSyncLock本身就是添加在主隊列里的任務(wù)就斤,方法里包含了dispatch_sync操作,所以需要先執(zhí)行block里的任務(wù)(即task1)蘑辑。而這個dispatch_sync操作是將task1添加到主隊列中洋机,所以又需要先等外層方法testMainQueueAndSyncLock執(zhí)行完才能執(zhí)行task1,這樣就造成外層方法和task1互相等待洋魂,阻塞了主隊列绷旗,導(dǎo)致主線程死鎖。

  • 主隊列+同步執(zhí)行不一定會死鎖

這里要特別說明一下副砍,之前有看過其他文章刁标,說主隊列+同步執(zhí)行一定會造成死鎖,其實是不準(zhǔn)確的址晕,只是因為“執(zhí)行主隊列+同步執(zhí)行的任務(wù)”本身也處在主隊列中同步執(zhí)行,所以會導(dǎo)致主線程死鎖顿锰。來看下面的例子:

-(void)testMainQueueAndSyncLock{
    
    NSLog(@" ==== method thread = %@", [NSThread currentThread]);
    NSLog(@" ==== begin");
    
    dispatch_queue_t queue = dispatch_queue_create("lab", DISPATCH_QUEUE_SERIAL);
    //添加異步執(zhí)行任務(wù)1
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:2];
        NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
        //添加同步執(zhí)行任務(wù)2到主隊列中
        dispatch_sync(dispatch_get_main_queue(), ^{
            
            [NSThread sleepForTimeInterval:2];
            NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
        });
    });

   //這里延遲8s
   [NSThread sleepForTimeInterval:8];
    NSLog(@" ==== end");
}

//打印結(jié)果
2020-11-16 15:22:38.153895+0800 GCDDemo[59402:75766119]  ==== method thread = <NSThread: 0x600001708280>{number = 1, name = main}
2020-11-16 15:22:38.153978+0800 GCDDemo[59402:75766119]  ==== begin
2020-11-16 15:22:40.156534+0800 GCDDemo[59402:75766421]  ==== task1 thread = <NSThread: 0x60000174b440>{number = 2, name = (null)}
2020-11-16 15:22:46.154269+0800 GCDDemo[59402:75766119]  ==== end
2020-11-16 15:22:48.247564+0800 GCDDemo[59402:75766119]  ==== task2 thread = <NSThread: 0x600001708280>{number = 1, name = main}

這里先往一個串行隊列queue中添加一個異步執(zhí)行任務(wù)task1谨垃,再在tsak1里往主隊列中添加同步執(zhí)行任務(wù)task2,從執(zhí)行結(jié)果可知硼控,并未死鎖。通過代碼邏輯可知,在這個過程中存在兩個隊列:主隊列和串行隊列queue酥诽。串行隊列queue中只有一個異步執(zhí)行的任務(wù)task1爽锥,不會造成死鎖,所以主要看主隊列中的情況熏版。

主隊列里存在兩個同步執(zhí)行任務(wù)纷责,會先執(zhí)行任務(wù)外層方法testMainQueueAndSyncLock捍掺,再執(zhí)行task2。外層方法里包含了NSLog再膳、sleepForTimeInterval挺勿、dispatch_async這幾個操作,而執(zhí)行dispatch_async的操作不需要先將其block里的任務(wù)執(zhí)行完喂柒,所以可以直接將外層方法執(zhí)行完不瓶,然后再執(zhí)行另一個任務(wù)task2,這樣兩個任務(wù)不會互相等待灾杰,也就不會導(dǎo)致主線程死鎖蚊丐。

5. 串行隊列分析后的結(jié)論

根據(jù)上面的分析可知,串行隊列在各場景下的使用情況如下:

  1. 當(dāng)一個串行隊列里都是同步執(zhí)行的任務(wù)艳吠,不會開辟新線程麦备,所有任務(wù)均在當(dāng)前線程同步執(zhí)行。
  2. 當(dāng)一個串行隊列里全是異步執(zhí)行的任務(wù)讲竿,只會開啟一個新線程泥兰,所有任務(wù)在這個線程里按順序同步執(zhí)行。
  3. 當(dāng)一個串行隊列中既有同步執(zhí)行任務(wù)题禀,還有異步執(zhí)行任務(wù)時鞋诗,會新開啟一個子線程,所有異步任務(wù)均在子線程上執(zhí)行迈嘹,所有同步任務(wù)均在當(dāng)前線程中執(zhí)行削彬,雖然是在兩個線程上執(zhí)行,但所有任務(wù)依舊按序同步執(zhí)行秀仲。
  4. 若在某個串行隊列中使用當(dāng)前串行隊列+同步執(zhí)行任務(wù)融痛,則會阻塞當(dāng)前隊列,導(dǎo)致當(dāng)前線程死鎖神僵。

五雁刷、對并發(fā)隊列的分析

并發(fā)隊列也遵循FIFO原則,和串行隊列一樣保礼,也會存在以下幾種情況:

  1. 全部都是同步執(zhí)行的任務(wù)
  2. 全部都是同步執(zhí)行的任務(wù)
  3. 既有同步執(zhí)行又有異步執(zhí)行的任務(wù)

接下來也通過具體示例來分析這幾種組合情況沛励。

1. 全部都是同步執(zhí)行的任務(wù)

如下面代碼所示,往一個并發(fā)隊列中添加三個同步執(zhí)行任務(wù)炮障,然后打印執(zhí)行結(jié)果目派。

//并發(fā)隊列+同步執(zhí)行任務(wù)
-(void)testConcurrentAndSync{
    
    NSLog(@" ==== method thread = %@", [NSThread currentThread]);
    NSLog(@" ==== begin");
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    //添加同步執(zhí)行任務(wù)1
    dispatch_sync(queue, ^{
        
        [NSThread sleepForTimeInterval:3];
        NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
    });
    //添加同步執(zhí)行任務(wù)2
    dispatch_sync(queue, ^{
        
        [NSThread sleepForTimeInterval:2];
        NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
    });
    //添加同步執(zhí)行任務(wù)3
    dispatch_sync(queue, ^{
        
        [NSThread sleepForTimeInterval:1];
        NSLog(@" ==== task3 thread = %@", [NSThread currentThread]);
    });
    
    NSLog(@" ==== end");
}

2020-11-16 16:03:40.559502+0800 GCDDemo[61734:76074945]  ==== method thread = <NSThread: 0x60000170ce40>{number = 1, name = main}
2020-11-16 16:03:40.559576+0800 GCDDemo[61734:76074945]  ==== begin
2020-11-16 16:03:43.559757+0800 GCDDemo[61734:76074945]  ==== task1 thread = <NSThread: 0x60000170ce40>{number = 1, name = main}
2020-11-16 16:03:45.560417+0800 GCDDemo[61734:76074945]  ==== task2 thread = <NSThread: 0x60000170ce40>{number = 1, name = main}
2020-11-16 16:03:46.560727+0800 GCDDemo[61734:76074945]  ==== task3 thread = <NSThread: 0x60000170ce40>{number = 1, name = main}
2020-11-16 16:03:46.560801+0800 GCDDemo[61734:76074945]  ==== end

從打印結(jié)果可知,這三個任務(wù)均在主線程中按序執(zhí)行胁赢。因此企蹭,當(dāng)一個并發(fā)隊列里都是同步執(zhí)行的任務(wù),不會開辟新線程,所有任務(wù)均在當(dāng)前線程同步執(zhí)行谅摄。

2. 全部都是異步執(zhí)行的任務(wù)

如下面代碼所示徒河,往一個并發(fā)隊列中添加三個異步執(zhí)行任務(wù),然后打印執(zhí)行結(jié)果螟凭。

//并發(fā)隊列+異步執(zhí)行任務(wù)
-(void)testConcurrentAndAsync{
    
    NSLog(@" ==== method thread = %@", [NSThread currentThread]);
    NSLog(@" ==== begin");
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    //添加同步執(zhí)行任務(wù)1
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:3];
        NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
    });
    //添加同步執(zhí)行任務(wù)2
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:2];
        NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
    });
    //添加同步執(zhí)行任務(wù)3
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:1];
        NSLog(@" ==== task3 thread = %@", [NSThread currentThread]);
    });
    
    NSLog(@" ==== end");
}

//打印結(jié)果
2020-11-16 16:08:30.368449+0800 GCDDemo[62020:76112767]  ==== method thread = <NSThread: 0x600001708600>{number = 1, name = main}
2020-11-16 16:08:30.368531+0800 GCDDemo[62020:76112767]  ==== begin
2020-11-16 16:08:30.368594+0800 GCDDemo[62020:76112767]  ==== end
2020-11-16 16:08:31.369037+0800 GCDDemo[62020:76113117]  ==== task3 thread = <NSThread: 0x60000176a980>{number = 2, name = (null)}
2020-11-16 16:08:32.372892+0800 GCDDemo[62020:76113114]  ==== task2 thread = <NSThread: 0x60000176c2c0>{number = 3, name = (null)}
2020-11-16 16:08:33.373147+0800 GCDDemo[62020:76113116]  ==== task1 thread = <NSThread: 0x600001767340>{number = 4, name = (null)}

從打印結(jié)果可知虚青,三個任務(wù)在三條不同的線程上異步執(zhí)行。因此螺男,當(dāng)一個并發(fā)隊列里都是異步執(zhí)行任務(wù)時棒厘,會給每一個任務(wù)開啟一條新線程,來異步執(zhí)行這些任務(wù)下隧。

3. 既有同步執(zhí)行又有異步執(zhí)行的任務(wù)

如下面代碼所示奢人,往一個并發(fā)隊列中穿插添加兩個同步執(zhí)行的任務(wù)和兩個異步執(zhí)行的任務(wù)。

//并發(fā)隊列+同步和異步執(zhí)行任務(wù)
-(void)testConcurrent{
    
    NSLog(@" ==== method thread = %@", [NSThread currentThread]);
    NSLog(@" ==== begin");
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    //添加異步執(zhí)行任務(wù)1
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:4];
        NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
    });
    //添加同步執(zhí)行任務(wù)2
    dispatch_sync(queue, ^{
        
        [NSThread sleepForTimeInterval:3];
        NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
    });
    //添加異步執(zhí)行任務(wù)3
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:2];
        NSLog(@" ==== task3 thread = %@", [NSThread currentThread]);
    });
    //添加同步執(zhí)行任務(wù)4
    dispatch_sync(queue, ^{
        
        [NSThread sleepForTimeInterval:2];
        NSLog(@" ==== task4 thread = %@", [NSThread currentThread]);
    });
    
    NSLog(@" ==== end");
}

//打印結(jié)果
2020-11-16 16:32:19.942009+0800 GCDDemo[63414:76291148]  ==== method thread = <NSThread: 0x600001704e80>{number = 1, name = main}
2020-11-16 16:32:19.942079+0800 GCDDemo[63414:76291148]  ==== begin
2020-11-16 16:32:22.942793+0800 GCDDemo[63414:76291148]  ==== task2 thread = <NSThread: 0x600001704e80>{number = 1, name = main}
2020-11-16 16:32:23.944156+0800 GCDDemo[63414:76291650]  ==== task1 thread = <NSThread: 0x600001742b00>{number = 2, name = (null)}
2020-11-16 16:32:24.943348+0800 GCDDemo[63414:76291148]  ==== task4 thread = <NSThread: 0x600001704e80>{number = 1, name = main}
2020-11-16 16:32:24.943353+0800 GCDDemo[63414:76291576]  ==== task3 thread = <NSThread: 0x600001742b80>{number = 3, name = (null)}
2020-11-16 16:32:24.943470+0800 GCDDemo[63414:76291148]  ==== end

從打印結(jié)果可知淆院,兩個同步任務(wù)均在主線程中按序執(zhí)行何乎,兩個異步任務(wù)在兩個不同的子線程中執(zhí)行(打印結(jié)果的順序后面再分析)。因此土辩,當(dāng)一個并發(fā)隊列中既有同步執(zhí)行任務(wù)支救,還有異步執(zhí)行任務(wù)時,會給每一個異步任務(wù)開啟一條新線程來異步執(zhí)行拷淘,而所有同步任務(wù)均在當(dāng)前線程中按順序執(zhí)行各墨。

4. 并發(fā)隊列分析后的結(jié)論

根據(jù)上面的分析可知,串行隊列在各場景下的使用情況如下:

  1. 當(dāng)一個并發(fā)隊列里都是同步執(zhí)行的任務(wù)启涯,不會開辟新線程贬堵,所有任務(wù)均在當(dāng)前線程同步執(zhí)行。
  2. 當(dāng)一個并發(fā)隊列里都是異步執(zhí)行任務(wù)時结洼,會給每一個任務(wù)開啟一條新線程黎做,來異步執(zhí)行這些任務(wù)
  3. 當(dāng)一個并發(fā)隊列中既有同步執(zhí)行任務(wù),還有異步執(zhí)行任務(wù)時松忍,會給每一個異步任務(wù)開啟一條新線程來異步執(zhí)行蒸殿,而所有同步任務(wù)均在當(dāng)前線程中按順序執(zhí)行。

六鸣峭、同步函數(shù)和異步函數(shù)的耗時情況

對相同復(fù)雜度的任務(wù)來說伟桅,dispacth_syncdispatch_async執(zhí)行時間是不一樣的。

  • dispacth_async
-(void)testAsync{
    
    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{

        NSLog(@" ==== async interval = %f", CFAbsoluteTimeGetCurrent() - time);
    });
    
    NSLog(@" ==== interval = %f", CFAbsoluteTimeGetCurrent() - time);
}

//打印結(jié)果
2020-11-16 19:05:31.131300+0800 GCDDemo[72585:77510232]  ==== interval = 0.000019
2020-11-16 19:05:31.131308+0800 GCDDemo[72585:77510854]  ==== async interval = 0.000029

從打印結(jié)果可以看出叽掘,dispatch_async函數(shù)雖然先執(zhí)行,但執(zhí)行相同復(fù)雜度任務(wù)所需的時間玖雁,還是比直接執(zhí)行要久更扁。

  • dispacth_async和dispatch_sync
-(void)testAsyncAndSync{
    
    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{

        NSLog(@" ==== async interval = %f", CFAbsoluteTimeGetCurrent() - time);
    });

    dispatch_sync(queue, ^{

        NSLog(@" ==== sync interval = %f", CFAbsoluteTimeGetCurrent() - time);
    });
}

//打印結(jié)果
2020-11-16 19:09:08.919239+0800 GCDDemo[72810:77540305]  ==== sync interval = 0.000033
2020-11-16 19:09:08.919398+0800 GCDDemo[72810:77540590]  ==== async interval = 0.000214

從打印結(jié)果可知,執(zhí)行相同復(fù)雜度的任務(wù)時,dispatch_async要比dispatch_sync所需時間更長浓镜。

六溃列、面試題解析

學(xué)習(xí)了前面的知識后,接下來對幾個高頻次面試題的輸出結(jié)果進(jìn)行解析膛薛。

1. 并發(fā)隊列 + 異步執(zhí)行
-(void)interview1{
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@" ==== 1");
    dispatch_async(queue, ^{
       
        NSLog(@" ==== 2");
        dispatch_async(queue, ^{
           
            NSLog(@" ==== 3");
        });
        NSLog(@" ==== 4");
    });
    NSLog(@" ==== 5");
}

//打印結(jié)果
2020-11-16 19:17:51.449472+0800 GCDDemo[73315:77606414]  ==== 1
2020-11-16 19:17:51.449562+0800 GCDDemo[73315:77606414]  ==== 5
2020-11-16 19:17:51.449575+0800 GCDDemo[73315:77606769]  ==== 2
2020-11-16 19:17:51.449634+0800 GCDDemo[73315:77606769]  ==== 4
2020-11-16 19:17:51.449646+0800 GCDDemo[73315:77606768]  ==== 3

這道題比較簡單听隐,分析思路:先整體,再局部哄啄。

  • 首先雅任,從整體上看,外層方法interview1是在主線程中執(zhí)行咨跌,里面的操作按順序執(zhí)行沪么,主要包含:NSLog(@" ==== 1")dispatch_async锌半、NSLog(@" ==== 5")禽车。由于dispatch_async是異步執(zhí)行無需等待,且比直接執(zhí)行形同復(fù)雜度的任務(wù)耗時更久刊殉,所以會先輸出1殉摔,5,再輸出dispatch_async函數(shù)block里的打印记焊。
  • 再看dispatch_async里的任務(wù)逸月,里面包含三個操作:NSLog(@" ==== 2")dispatch_async亚亲、NSLog(@" ==== 4")彻采。這三個操作在新開啟的子線程里按順序執(zhí)行,和interview1里的操作一樣捌归,輸出順序為:2肛响、4、3惜索。

因此特笋,經(jīng)過分析得知,這道題的輸出順序為:1巾兆、5猎物、2、4角塑、3蔫磨。

這里將并發(fā)隊列改為串行隊列,輸出順序還是一樣圃伶,大家可以自己分析下堤如。

2. 并發(fā)隊列 + 異步函數(shù)嵌套同步函數(shù)
-(void)interview2{
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@" ==== 1");
    dispatch_async(queue, ^{
       
        NSLog(@" ==== 2");
        dispatch_sync(queue, ^{
           
            NSLog(@" ==== 3");
        });
        NSLog(@" ==== 4");
    });
    NSLog(@" ==== 5");
}

//打印結(jié)果
2020-11-16 19:44:38.915026+0800 GCDDemo[74906:77810083]  ==== 1
2020-11-16 19:44:38.915117+0800 GCDDemo[74906:77810083]  ==== 5
2020-11-16 19:44:38.915127+0800 GCDDemo[74906:77810255]  ==== 2
2020-11-16 19:44:38.915168+0800 GCDDemo[74906:77810255]  ==== 3
2020-11-16 19:44:38.915201+0800 GCDDemo[74906:77810255]  ==== 4

這道題只是在上一題上做個小改動蒲列,將里面的dispatch_async改為dispatch_sync。所以分析思路還是一樣:先整體搀罢,再局部蝗岖。

  • 從整體上看,還是先輸出1榔至、5抵赢,再輸出dispatch_async函數(shù)block里的任務(wù)。
  • dispatch_async里的任務(wù)來看唧取,三個操作NSLog(@" ==== 2")铅鲤、dispatch_sync以及NSLog(@" ==== 4")在新開的子線程中同步執(zhí)行,所以先輸出2兵怯,而dispatch_sync是同步函數(shù)彩匕,所以需要等待其block里的任務(wù)執(zhí)行完,才能執(zhí)行后面的任務(wù)媒区,所以先輸出3驼仪,再輸出4。

因此袜漩,經(jīng)過分析得知绪爸,這道題的輸出順序為:1、5宙攻、2奠货、3、4座掘。

3. 串行隊列 + 異步函數(shù)嵌套同步函數(shù)
-(void)interview3{
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
    NSLog(@" ==== 1");
    dispatch_async(queue, ^{
       
        NSLog(@" ==== 2");
        dispatch_sync(queue, ^{
           
            NSLog(@" ==== 3");
        });
        NSLog(@" ==== 4");
    });
    NSLog(@" ==== 5");
}

//打印結(jié)果
2020-11-16 20:06:27.263232+0800 GCDDemo[76170:77980426]  ==== 1
2020-11-16 20:06:27.263325+0800 GCDDemo[76170:77980426]  ==== 5
2020-11-16 20:06:27.263338+0800 GCDDemo[76170:77980465]  ==== 2

//報錯
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
dispatch_sync_f_slow

這里輸出1递惋、5、2后就報錯溢陪,因為任務(wù)互相等待萍虽,阻塞了串行隊列queue,導(dǎo)致子線程死鎖了(具體分析查在上文的『普通串行隊列死鎖』)形真。

4. 并發(fā)隊列 + 異步函數(shù)嵌套同步函數(shù)
-(void)interview4{
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        
        NSLog(@" ==== 1");
    });
    dispatch_async(queue, ^{
        
        NSLog(@" ==== 2");
    });
    dispatch_sync(queue, ^{
        
        NSLog(@" ==== 3");
    });
    NSLog(@" ==== 0");
    dispatch_async(queue, ^{
        
        NSLog(@" ==== 7");
    });
    dispatch_async(queue, ^{
        
        NSLog(@" ==== 8");
    });
    dispatch_async(queue, ^{
        
        NSLog(@" ==== 9");
    });
}

//打印結(jié)果
2020-11-16 21:36:24.898847+0800 GCDDemo[81805:78779526]  ==== 3
2020-11-16 21:36:24.898907+0800 GCDDemo[81805:78779885]  ==== 2
2020-11-16 21:36:24.898880+0800 GCDDemo[81805:78779883]  ==== 1
2020-11-16 21:36:24.898930+0800 GCDDemo[81805:78779526]  ==== 0
2020-11-16 21:36:24.898997+0800 GCDDemo[81805:78779883]  ==== 7
2020-11-16 21:36:24.899024+0800 GCDDemo[81805:78779885]  ==== 8
2020-11-16 21:36:24.899045+0800 GCDDemo[81805:78779883]  ==== 9

這里的輸出結(jié)果只是其中的一種情況杉编,還有其他情況。從前面對并發(fā)隊列分析后的結(jié)論可知咆霜,當(dāng)一個并發(fā)隊列中既有同步執(zhí)行任務(wù)邓馒,還有異步執(zhí)行任務(wù)時,會給每一個異步任務(wù)開啟一條新線程來異步執(zhí)行蛾坯,而所有同步任務(wù)均在當(dāng)前線程中按順序執(zhí)行光酣。因此這題的分析思路:先同步,再異步脉课。

  • 30的輸出是處于外層方法interview4中的同步執(zhí)行任務(wù)救军,所以0必定會在3后輸出改览。
  • 78缤言、9三個輸出任務(wù)的dispatch_async操作是在外層方法interview4中同步執(zhí)行的,且在同步任務(wù)NSLog(@" ==== 0")后面视事,所以7胆萧、8、9必定在0后輸出俐东。
  • 1跌穗、27虏辫、8蚌吸、9這五個輸出任務(wù)均在并發(fā)隊列queue中,且會在不同的子線程上異步執(zhí)行砌庄,不需要等待羹唠,所以它們的執(zhí)行不分先后

因此娄昆,經(jīng)過分析可知佩微,0必定在3的后面輸出,7萌焰、8哺眯、9必定在0的后面輸出,而根據(jù)任務(wù)的復(fù)雜度不同扒俯,1奶卓,2在任何位置出現(xiàn)都有可能。

推薦閱讀

1. iOS原理 多線程1 -- 多線程的作用和原理
2. iOS 多線程:『GCD』詳盡總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末撼玄,一起剝皮案震驚了整個濱河市夺姑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌互纯,老刑警劉巖瑟幕,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異留潦,居然都是意外死亡只盹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門兔院,熙熙樓的掌柜王于貴愁眉苦臉地迎上來殖卑,“玉大人,你說我怎么就攤上這事坊萝》趸” “怎么了许起?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長菩鲜。 經(jīng)常有香客問我园细,道長,這世上最難降的妖魔是什么接校? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任猛频,我火速辦了婚禮,結(jié)果婚禮上蛛勉,老公的妹妹穿的比我還像新娘鹿寻。我一直安慰自己,他們只是感情好诽凌,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布毡熏。 她就那樣靜靜地躺著,像睡著了一般侣诵。 火紅的嫁衣襯著肌膚如雪痢法。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天窝趣,我揣著相機(jī)與錄音疯暑,去河邊找鬼。 笑死哑舒,一個胖子當(dāng)著我的面吹牛妇拯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播洗鸵,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼越锈,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了膘滨?” 一聲冷哼從身側(cè)響起甘凭,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎火邓,沒想到半個月后丹弱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡铲咨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年躲胳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纤勒。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡坯苹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出摇天,到底是詐尸還是另有隱情粹湃,我是刑警寧澤恐仑,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站为鳄,受9級特大地震影響裳仆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜孤钦,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一鉴逞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧司训,春花似錦、人聲如沸液南。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽滑凉。三九已至统扳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間畅姊,已是汗流浹背咒钟。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留若未,地道東北人朱嘴。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像粗合,于是被迫代替她去往敵國和親萍嬉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

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