iOS多線程(二)--GCD詳解

目錄:

iOS多線程(一)--pthread着撩、NSThread
iOS多線程(二)--GCD詳解
iOS多線程(三)--NSOperation詳解

1. GCD簡(jiǎn)介

Grand Central Dispatch (GCD) 是Apple開(kāi)發(fā)的一個(gè)多核編程的較新的解決方法鸠澈。它主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對(duì)稱多處理系統(tǒng)即纲。它是一個(gè)在線程池模式的基礎(chǔ)上執(zhí)行的并行任務(wù)稼锅。
GCD有很多優(yōu)點(diǎn)具體如下:
1)GCD可用于多核的并行運(yùn)算
2)GCD會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核)
3)GCD會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程切揭、調(diào)度任務(wù)呀狼、銷毀線程)
4)程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫(xiě)任何線程管理代碼

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

學(xué)習(xí)GCD之前里烦,先來(lái)了解GCD中兩個(gè)核心概念:任務(wù)和隊(duì)列凿蒜。

  1. 任務(wù):就是執(zhí)行操作的意思,換句話說(shuō)就是你在線程中執(zhí)行的那段代碼胁黑。在GCD中是放在block中的废封。執(zhí)行任務(wù)有兩種方式:同步執(zhí)行和異步執(zhí)行。兩者的主要區(qū)別是:是否具備開(kāi)啟新線程的能力丧蘸。
  • 同步執(zhí)行(sync):只能在當(dāng)前線程中執(zhí)行任務(wù)漂洋,不具備開(kāi)啟新線程的能力
  • 異步執(zhí)行(async):可以在新的線程中執(zhí)行任務(wù),具備開(kāi)啟新線程的能力
  1. 這里的隊(duì)列指任務(wù)隊(duì)列触趴,即用來(lái)存放任務(wù)的隊(duì)列氮发。隊(duì)列是一種特殊的線性表,采用FIFO(先進(jìn)先出)的原則冗懦,即新任務(wù)總是被插入到隊(duì)列的末尾,而讀取任務(wù)的時(shí)候總是從隊(duì)列的頭部開(kāi)始讀取仇祭。每讀取一個(gè)任務(wù)披蕉,則從隊(duì)列中釋放一個(gè)任務(wù)。在GCD中有兩種隊(duì)列:串行隊(duì)列和并行隊(duì)列乌奇。
  • 并行隊(duì)列(Concurrent Dispatch Queue):可以讓多個(gè)任務(wù)并行(同時(shí))執(zhí)行(自動(dòng)開(kāi)啟多個(gè)線程同時(shí)執(zhí)行任務(wù))没讲。并行功能只有在異步(dispatch_async)函數(shù)下才有效(應(yīng)該是目前開(kāi)啟了多少線程,才有多少個(gè)任務(wù)才并行執(zhí)行吧)礁苗。注意特殊的并行隊(duì)列-全局并行隊(duì)列:dispatch_get_global_queue來(lái)創(chuàng)建全局并行隊(duì)列爬凑。GCD默認(rèn)提供了全局的并行隊(duì)列,需要傳入兩個(gè)參數(shù)试伙。第一個(gè)參數(shù)表示隊(duì)列優(yōu)先級(jí)嘁信,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二個(gè)參數(shù)暫時(shí)沒(méi)用疏叨,用0即可潘靖。
  • 串行隊(duì)列(Serial Dispatch Queue):讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù))蚤蔓。注意特殊的串行隊(duì)列-主隊(duì)列:dispatch_get_main_queue()獲得主隊(duì)列, 所有放在主隊(duì)列中的任務(wù)卦溢,都會(huì)放到主線程中執(zhí)行。

3. GCD的使用

GCD的使用步驟其實(shí)很簡(jiǎn)單,只有兩步单寂。
1.創(chuàng)建一個(gè)隊(duì)列(串行隊(duì)列或并行隊(duì)列)
2.將任務(wù)添加到隊(duì)列中贬芥,然后系統(tǒng)就會(huì)根據(jù)任務(wù)類型執(zhí)行任務(wù)(同步執(zhí)行或異步執(zhí)行)

下邊來(lái)看看隊(duì)列的創(chuàng)建方法和任務(wù)的創(chuàng)建方法。

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

可以使用dispatch_queue_create來(lái)創(chuàng)建對(duì)象宣决,需要傳入兩個(gè)參數(shù)蘸劈,第一個(gè)參數(shù)表示隊(duì)列的唯一標(biāo)識(shí)符,用于DEBUG疲扎,可為空昵时;第二個(gè)參數(shù)用來(lái)識(shí)別是串行隊(duì)列還是并行隊(duì)列。DISPATCH_QUEUE_SERIAL表示串行隊(duì)列椒丧,DISPATCH_QUEUE_CONCURRENT表示并行隊(duì)列壹甥。

// 串行隊(duì)列的創(chuàng)建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并行隊(duì)列的創(chuàng)建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

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

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

3.3 GCD的組合方式

隊(duì)列的不同和任務(wù)執(zhí)行方式的不同可以組成以下6種組合,尤其注意后兩種主隊(duì)列(主隊(duì)列是一種特殊的串行隊(duì)列壶熏,主隊(duì)列里的任務(wù)只在主線程執(zhí)行)的組合:

  1. 并行隊(duì)列 + 同步執(zhí)行
  2. 并行隊(duì)列 + 異步執(zhí)行
  3. 串行隊(duì)列 + 同步執(zhí)行
  4. 串行隊(duì)列 + 異步執(zhí)行
  5. 主隊(duì)列 + 同步執(zhí)行
  6. 主隊(duì)列 + 異步執(zhí)行

不同組合方式效果不同句柠,直接參看表格如下:

并行隊(duì)列 串行隊(duì)列 主隊(duì)列
同步(sync) 沒(méi)有開(kāi)啟新線程,串行執(zhí)行任務(wù) 沒(méi)有開(kāi)啟新線程棒假,串行執(zhí)行任務(wù) 沒(méi)有開(kāi)啟新線程溯职,串行執(zhí)行任務(wù)
異步(async) 有開(kāi)啟新線程,并行執(zhí)行任務(wù) 有開(kāi)啟新線程(1條)帽哑,串行執(zhí)行任務(wù) 沒(méi)有開(kāi)啟新線程谜酒,串行執(zhí)行任務(wù)

相信網(wǎng)上其他帖子的示例已經(jīng)很多了,我這里只列舉下主隊(duì)列的同步執(zhí)行和異步執(zhí)行妻枕,其他的便不再一一贅述:

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

- (void)syncMain
{
    NSLog(@"syncMain---begin");

    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });   

    NSLog(@"syncMain---end");
}

輸出結(jié)果
2016-09-03 19:32:15.356 GCD[11670:1908306] syncMain---begin

我們發(fā)現(xiàn)輸出begin后則沒(méi)有再繼續(xù)往下執(zhí)行了僻族,現(xiàn)在來(lái)具體分析原因:我們?cè)谥麝?duì)列上增加同步任務(wù),同步任務(wù)有個(gè)特點(diǎn)是立即執(zhí)行屡谐,而主隊(duì)列里的任務(wù)一定是在主線程執(zhí)行的述么,但主線程正在執(zhí)行syncMain這個(gè)方法,我們?cè)黾拥街麝?duì)列里的任務(wù)必須要等syncMain方法執(zhí)行完之后才能執(zhí)行愕掏,但我們很容易發(fā)現(xiàn)syncMain方法由于方法體里的同步任務(wù)是無(wú)法執(zhí)行完的度秘,這便是死鎖原因所在。

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

- (void)asyncMain
{
    NSLog(@"asyncMain---begin");

    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });  

    NSLog(@"asyncMain---end");
}

輸出結(jié)果:
2016-09-03 19:33:54.995 GCD[11706:1911313] asyncMain---begin
2016-09-03 19:33:54.996 GCD[11706:1911313] asyncMain---end
2016-09-03 19:33:54.996 GCD[11706:1911313] 1------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 1------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 2------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 2------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 3------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 3------<NSThread: 0x7fb623d015e0>{number = 1, name = main}

分析上面輸出饵撑,由于是在主隊(duì)列上異步執(zhí)行剑梳,不需要立即執(zhí)行,但是在主隊(duì)列上的任務(wù)必須是在主線程執(zhí)行肄梨,所以往主隊(duì)列上添加的三個(gè)異步任務(wù)是順序一一輸出結(jié)果的阻荒。

4 GCD線程之間的通訊

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

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for (int i = 0; i < 2; ++i) {
        NSLog(@"1------%@",[NSThread currentThread]);
    }

    // 回到主線程
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"2-------%@",[NSThread currentThread]);
    });
});

5 GCD的其他使用

5.1 多用派發(fā)隊(duì)列情妖,少用同步鎖

如果有多個(gè)線程要執(zhí)行同一份代碼睬关,則可能出現(xiàn)問(wèn)題。在之前我們使用鎖來(lái)實(shí)現(xiàn)同步機(jī)制毡证,具體實(shí)現(xiàn)有以下兩種方式:

//1. 內(nèi)置的同步塊
- (void)synchronizedMethod {
    @synchronized (self) {
        //save
    }
}
//2. NSLock對(duì)象
_lock = [[NSLock alloc] init];

- (void)synchronizedMethod {
    [_lock lock];
    //safe
    [_lock unlock];
}

但是濫用@synchronized (self)同步塊非常危險(xiǎn)电爹,因?yàn)樗型綁K都會(huì)搶奪同一個(gè)鎖,比如一個(gè)類里有很多屬性都需要同步時(shí)料睛,那么每個(gè)屬性的同步塊都得等其他所有的同步塊執(zhí)行完丐箩,但我們只是想要每個(gè)屬性各自獨(dú)立的同步即可。而且synchronized要求被其鎖住的對(duì)象一定要正確恤煞,不能應(yīng)該鎖A屎勘,但其實(shí)鎖了B。
然后直接使用NSLock對(duì)象的話居扒,一旦遇到死鎖也是會(huì)非常的麻煩概漱。
針對(duì)以上兩種情況,GCD提供了一種非常簡(jiǎn)單的解決方案喜喂,就是串行同步隊(duì)列犀概,將讀取操作和寫(xiě)入操作都寫(xiě)入同一個(gè)隊(duì)列,即可保證數(shù)據(jù)同步夜惭。

5.2 多用派發(fā)隊(duì)列,少用performSelector系列方法

performSelector系列有以下方法:

// NSObject (NSDelayedPerforming)
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;

//NSObject (返回對(duì)象是id铛绰,如果遇到返回對(duì)象是void或者基礎(chǔ)數(shù)據(jù)類型就需要經(jīng)過(guò)一番轉(zhuǎn)換诈茧,而且最多能帶兩個(gè)參數(shù))
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

下面我們來(lái)看performSelector方法的使用,如下例;

    SEL selector;
    if (/*conditon 1*/) {
        selector = @selector(fun1);
    }else if(/*conditon 2*/) {
        selector = @selector(fun2);
    }else {
        selector = @selector(fun3);
    }

代碼看起來(lái)沒(méi)什么問(wèn)題捂掰,但是編譯器卻給出了警告信息:

warning:performSelector may cause a leak beacause its selector

原因在于編譯器不知道將要調(diào)用的選擇子敢会,因此不了解其方法名及其返回值,所以沒(méi)辦法運(yùn)用ARC的內(nèi)存管理規(guī)則來(lái)判斷返回值是不是該釋放这嚣,因此ARC采用了比較謹(jǐn)慎的辦法就是不添加釋放操作鸥昏。但這么做可能導(dǎo)致內(nèi)存泄露,因?yàn)榉祷刂翟诒环祷貢r(shí)被方法保留了姐帚。
想把某任務(wù)放到主線程執(zhí)行吏垮,可以有以下兩種選擇方式,但我們還是應(yīng)該選擇GCD

//1.
[self performSelectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO];

//2.
dispatch_async(dispatch_get_main_queue(), ^{
        //[self doSomething]
    });

5.3 dispatch_after

延后執(zhí)行也有兩種選擇方式,我們依然應(yīng)該選擇GCD

//1.
[self performSelectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO];

//2.
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,(int64_t)(5.0*NSEC_PER_SEC));
    dispatch_after(time, dispatch_get_main_queue(), ^{
        //[self doSomething]
    });

5.4 dispatch_group膳汪,根據(jù)系統(tǒng)資源狀態(tài)來(lái)執(zhí)行任務(wù)

dispatch_group能把任務(wù)分組唯蝶,調(diào)用者可以等待這組任務(wù)執(zhí)行完畢,也可以在提供回調(diào)函數(shù)后繼續(xù)往下執(zhí)行遗嗽,這組任務(wù)完成后粘我,調(diào)用者會(huì)得到通知。這個(gè)功能有許多用途痹换,最重要以及最值得注意的是要將并發(fā)執(zhí)行的多個(gè)任務(wù)合為一個(gè)組征字,于是調(diào)用者就能知道這組任務(wù)什么時(shí)候才能全部執(zhí)行完畢。
dispatch_group有以下幾個(gè)相關(guān)方法:

5.4.1 dispatch_group_create

dispatch_group_t group =  dispatch_group_create(); //創(chuàng)建任務(wù)分組

5.4.2 dispatch_group_async

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

5.4.3 dispatch_group_enter

用于添加對(duì)應(yīng)任務(wù)組中的未執(zhí)行完畢的任務(wù)數(shù),執(zhí)行一次锤躁,未執(zhí)行完畢的任務(wù)數(shù)加1搁料,當(dāng)未執(zhí)行完畢任務(wù)數(shù)為0的時(shí)候,才會(huì)使dispatch_group_wait解除阻塞和dispatch_group_notify的block執(zhí)行

void dispatch_group_enter(dispatch_group_t group);

5.4.4 dispatch_group_leave

用于減少任務(wù)組中的未執(zhí)行完畢的任務(wù)數(shù)系羞,執(zhí)行一次郭计,未執(zhí)行完畢的任務(wù)數(shù)減1,dispatch_group_enter和dispatch_group_leave要匹配椒振,不然系統(tǒng)會(huì)認(rèn)為group任務(wù)沒(méi)有執(zhí)行完畢

void dispatch_group_leave(dispatch_group_t group);

5.4.5 dispatch_group_wait

等待組任務(wù)完成昭伸,會(huì)阻塞當(dāng)前線程,當(dāng)任務(wù)組執(zhí)行完畢時(shí)澎迎,才會(huì)解除阻塞當(dāng)前線程

long dispatch_group_wait(dispatch_group_t group, 
                         dispatch_time_t timeout); 
  • group ——需要等待的任務(wù)組
  • timeout ——等待的超時(shí)時(shí)間(即等多久)庐杨,單位為dispatch_time_t。如果設(shè)置為DISPATCH_TIME_FOREVER,則會(huì)一直等待(阻塞當(dāng)前線程)夹供,直到任務(wù)組執(zhí)行完畢

5.4.6 dispatch_group_notify

待任務(wù)組執(zhí)行完畢時(shí)調(diào)用灵份,不會(huì)阻塞當(dāng)前線程

void dispatch_group_notify(dispatch_group_t group,
                           dispatch_queue_t queue, 
                           dispatch_block_t block);
  • group ——需要監(jiān)聽(tīng)的任務(wù)組
  • queue ——block任務(wù)執(zhí)行的線程隊(duì)列,和之前group執(zhí)行的線程隊(duì)列無(wú)關(guān)
  • block ——任務(wù)組執(zhí)行完畢時(shí)需要執(zhí)行的任務(wù)block

5.4.7 使用示例

最常用的用法如下:

dispatch_group_t group =  dispatch_group_create();
NSLog(@"group one start");
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"group one finish");
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"group finished");
});

輸出結(jié)果如下:
2017-09-11 16:09:00.125 DispatchGroupDemo[254:11537] group one start
2017-09-11 16:09:00.127 DispatchGroupDemo[254:11569] group one finish
2017-09-11 16:09:00.135 DispatchGroupDemo[254:11537] group finished

我們?cè)谏厦娴膁emo上深挖一下哮洽,如果是在組里執(zhí)行異步任務(wù)呢填渠?

        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        NSLog(@"group one start");
    
        dispatch_group_async(group, queue, ^{
            dispatch_async(queue, ^{
                sleep(1); //這里線程睡眠1秒鐘,模擬異步請(qǐng)求
                NSLog(@"group one finish");
            });
        });
    
        dispatch_group_notify(group, queue, ^{
            NSLog(@"group finished");
        });

輸出結(jié)果如下:
2017-09-11 16:12:42.732 DispatchGroupDemo[263:12355] group one start
2017-09-11 16:12:42.734 DispatchGroupDemo[263:12379] group finished
2017-09-11 16:12:43.741 DispatchGroupDemo[263:12378] group one finish

我們發(fā)現(xiàn)輸出結(jié)果并不是我們想要的鸟辅,在group中嵌套了一個(gè)異步任務(wù)時(shí)氛什,group并沒(méi)有等待group內(nèi)的異步任務(wù)執(zhí)行完畢才進(jìn)入dispatch_group_notify中,這是因?yàn)榉肆梗赿ispatch_group_async中又啟了一個(gè)異步線程枪眉,而異步線程是直接返回的,所以group就認(rèn)為是執(zhí)行完畢了再层。
為了解決這個(gè)問(wèn)題贸铜,需要引入dispatch_group_enter和dispatch_group_leave的使用了堡纬。

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"group one start");
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        dispatch_async(queue, ^{
            sleep(1); //這里線程睡眠1秒鐘,模擬異步請(qǐng)求
            NSLog(@"group one finish");
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"group finished");
    });

輸出結(jié)果如下:
2017-09-11 16:16:39.149 DispatchGroupDemo[281:13448] group one start
2017-09-11 16:16:40.156 DispatchGroupDemo[281:13469] group one finish
2017-09-11 16:16:40.157 DispatchGroupDemo[281:13469] group finished

5.5 dispatch柵欄方法

有時(shí)需要異步執(zhí)行兩組操作萨脑,而且第一組操作執(zhí)行完之后隐轩,才能開(kāi)始執(zhí)行第二組操作。這樣我們就需要一個(gè)相當(dāng)于柵欄一樣的一個(gè)方法將兩組異步執(zhí)行的操作組給分割起來(lái)渤早,當(dāng)然這里的操作組里可以包含一個(gè)或多個(gè)任務(wù)职车。

假設(shè)我們?cè)扔?個(gè)任務(wù)要執(zhí)行,我們現(xiàn)在要插入一個(gè)任務(wù)0鹊杖,這個(gè)任務(wù)0要在1悴灵、2、3都并發(fā)執(zhí)行完了之后才能執(zhí)行骂蓖,而4积瞒、5、6號(hào)任務(wù)要在這個(gè)任務(wù)0結(jié)束后才允許并發(fā)登下。對(duì)于這樣一種需求茫孔,很多朋友的第一反應(yīng)就是用個(gè)group就解決了。確實(shí)如此被芳,但是系統(tǒng)提供了一種更加簡(jiǎn)單地方法缰贝,那就是dispatch柵欄方法。
這就需要用到dispatch_barrier_async/dispatch_barrier_sync方法在兩個(gè)操作組間形成柵欄畔濒。
下面我們具體來(lái)看看dispatch_barrier_async和dispatch_barrier_sync的區(qū)別:

//dispatch_barrier_async示例
- (IBAction)testBarrierAsync:(id)sender {
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"group one start");
    
    dispatch_async(queue, ^{
        NSLog(@"task 1 finish");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"task 2 finish");
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"== %@ == barrier finished",[NSThread currentThread]);
    });
    
    NSLog(@"aaa");
    dispatch_async(queue, ^{
        NSLog(@"task 3 finish");
    });
    
    NSLog(@"bbb");
    dispatch_async(queue, ^{
        NSLog(@"task 4 finish");
    });

輸出結(jié)果如下:
2017-09-12 19:24:10.409 TestGCD[1142:139438] group one start
2017-09-12 19:24:10.410 TestGCD[1142:139438] aaa
2017-09-12 19:24:10.410 TestGCD[1142:139438] bbb
2017-09-12 19:24:11.419 TestGCD[1142:139792] task 2 finish
2017-09-12 19:24:11.419 TestGCD[1142:139770] task 1 finish
2017-09-12 19:24:11.420 TestGCD[1142:139770] == <NSThread: 0x17027ce00>{number = 4, name = (null)} == barrier finished
2017-09-12 19:24:11.420 TestGCD[1142:139770] task 3 finish
2017-09-12 19:24:11.421 TestGCD[1142:139770] task 4 finish

我們現(xiàn)在將上面的示例里的dispatch_barrier_async改成dispatch_barrier_sync試試剩晴,其他的代碼不變,則會(huì)發(fā)現(xiàn)輸出變成了:

輸出結(jié)果如下:
2017-09-12 19:25:30.066 TestGCD[1142:139438] group one start
2017-09-12 19:25:30.067 TestGCD[1142:139927] task 1 finish
2017-09-12 19:25:30.068 TestGCD[1142:139927] task 2 finish
2017-09-12 19:25:30.069 TestGCD[1142:139438] == <NSThread: 0x17007f000>{number = 1, name = main} == barrier finished
2017-09-12 19:25:30.069 TestGCD[1142:139438] aaa
2017-09-12 19:25:30.070 TestGCD[1142:139438] bbb
2017-09-12 19:25:30.072 TestGCD[1142:139941] task 3 finish
2017-09-12 19:25:30.073 TestGCD[1142:139927] task 4 finish

根據(jù)輸出結(jié)果我們可以看出侵状,barrier塊是肯定在1赞弥,2執(zhí)行完之后,3趣兄,4執(zhí)行完之前輸出的绽左,但aaa和bbb的輸出位置卻不一樣⊥叮總結(jié)下dispatch_barrier_async與dispatch_barrier_sync的異同點(diǎn)如下妇菱。

相同點(diǎn):
  • 都會(huì)等待在它前面插入隊(duì)列的任務(wù)(1、2)先執(zhí)行完
  • 都會(huì)等待他們自己的任務(wù)(barrier)執(zhí)行完再執(zhí)行后面的任務(wù)(3暴区、4)
不同點(diǎn):
  • dispatch_barrier_sync需要等待自己的任務(wù)(barrier)結(jié)束之后才會(huì)繼續(xù)程序,然后插入被寫(xiě)在它后面的任務(wù)(3辛臊、4)仙粱,然后執(zhí)行后面的任務(wù)
  • dispatch_barrier_async將自己的任務(wù)(barrier)插入到queue之后,不會(huì)等待自己的任務(wù)結(jié)束彻舰,它會(huì)繼續(xù)把后面的任務(wù)(3伐割、4)插入到queue候味。dispatch_barrier_async的不等待(異步)特性體現(xiàn)在將任務(wù)插入隊(duì)列的過(guò)程,它的等待特性體現(xiàn)在任務(wù)真正執(zhí)行的過(guò)程隔心。

關(guān)于barrier這塊的使用我在實(shí)際寫(xiě)示例時(shí)遇到一個(gè)問(wèn)題白群,當(dāng)將dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);這句換成系統(tǒng)的全局隊(duì)列時(shí),輸出結(jié)果不盡人意硬霍,目前我也沒(méi)有弄懂為什么帜慢,在此處暫時(shí)留一個(gè)疑問(wèn)。

看葉孤城的iOS進(jìn)階指南時(shí)唯卖,他給出了這樣的解釋:

在global queue中使用barrier沒(méi)有意義粱玲,為什么?因?yàn)閎arrier實(shí)現(xiàn)的基本條件是拜轨,要寫(xiě)在同一隊(duì)列中抽减。舉個(gè)例子,你現(xiàn)在創(chuàng)建了兩個(gè)并行隊(duì)列橄碾,你在其中一個(gè)隊(duì)列中插入了一個(gè)barrier任務(wù)卵沉,那么你不可能期待他在第二個(gè)隊(duì)列里生效,對(duì)吧法牲,同樣的史汗,每一次使用global queue,系統(tǒng)分配給你的可能是不同的并行隊(duì)列皆串,你在其中插入一個(gè)barrier任務(wù)淹办,又有什么意義呢?

5.7 dispatch_semaphore信號(hào)量

信號(hào)量的用法相當(dāng)簡(jiǎn)單恶复,一共有是三個(gè)方法怜森。

dispatch_semaphore_create    //創(chuàng)建一個(gè)信號(hào)量
dispatch_semphore_signal      //發(fā)送一個(gè)信號(hào)
dispatch_semaphore_wait       //等待信號(hào)

dispatch_semaphore的使用場(chǎng)景是處理并發(fā)控制,類似于NSOperationQueue的maxConcurrentOperationCount屬性谤牡,意思就是設(shè)定NSOperationQueue里的NSOperation同時(shí)運(yùn)行的最大數(shù)量副硅。
信號(hào)量同樣可以實(shí)現(xiàn)這樣的功能,

  1. 首先創(chuàng)建一個(gè)信號(hào)量:
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    創(chuàng)建方法里會(huì)傳入一個(gè)long型參數(shù)翅萤,這個(gè)東西你可以想象是一個(gè)庫(kù)存恐疲,有了庫(kù)存才可以出貨。
  2. dispatch_semaphore_wait,每運(yùn)行一次套么,就會(huì)清一個(gè)庫(kù)存培己,如果庫(kù)存為0,則根據(jù)傳入的等待時(shí)間胚泌,決定等待增加庫(kù)存的時(shí)間省咨,如果設(shè)置為DISPATCH_TIME_FOREVER,那么意思就是永遠(yuǎn)等待增加庫(kù)存玷室,否則就永遠(yuǎn)不會(huì)往下面走
  3. dispatch_semaphore_signal零蓉,每運(yùn)行一次笤受,增加一個(gè)庫(kù)存
    現(xiàn)在我們嘗試用dispatch_semaphore來(lái)實(shí)現(xiàn)并發(fā)的數(shù)量控制:
@implementation CustomOperationQueue
- (id)initWithConcurrentCount:(int)count {
     self = [super init];
     if(self) {
          if(count < 1) count = 5;
          semaphore = disptach_semaphore_create(count);
          queue = diapatch_queue_create("com.www",DISPATCH_QUEUE_CONCURRENT);
     }
     return self;
}

- (id)init {
     return [self initWithConcurrentCount:5];
}

- (void)addTask:(CallBackBlock)block {
     dispatch_async(queue,^{
           dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
           dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),0,^{
                 block();
                 dispatch_semaphore_signal(semaphore);
            });
     });
}

來(lái)解讀這段代碼:

  1. 創(chuàng)建了一個(gè)初始庫(kù)存是5的信號(hào)量
  2. 在addTask方法里,由于初始庫(kù)存是5敌蜂,所以第一次添加了一個(gè)任務(wù)之后箩兽,dispatch_semaphore_wait直接放行,并減少一個(gè)庫(kù)存章喉。當(dāng)完成一個(gè)任務(wù)之后汗贫,還回去一個(gè)庫(kù)存,調(diào)用dispatch_semaphore_signal增加一個(gè)庫(kù)存
  3. 那么囊陡,當(dāng)我們每個(gè)任務(wù)耗時(shí)都特別長(zhǎng)時(shí)芳绩,一直消耗庫(kù)存卻沒(méi)有增加庫(kù)存,那么添加到第6個(gè)任務(wù)時(shí)撞反,庫(kù)存為0妥色,那么wait會(huì)一直等待不執(zhí)行第六個(gè)任務(wù),直到庫(kù)存再次大于0時(shí)才會(huì)執(zhí)行

5.7 dispatch_apply快速迭代

dispatch_apply類似一個(gè)for循環(huán)遏片,會(huì)在指定的dispatch queue中運(yùn)行block任務(wù)n次嘹害,如果隊(duì)列是并發(fā)隊(duì)列,則會(huì)并發(fā)執(zhí)行block任務(wù)吮便,dispatch_apply是一個(gè)同步調(diào)用笔呀,block任務(wù)執(zhí)行n次后才返回。
在某些場(chǎng)景下髓需,使用dispatch_apply會(huì)有很大的性能提升许师。例如你的代碼需要以每個(gè)像素為基準(zhǔn)來(lái)處理計(jì)算image圖片。同時(shí)dispatch apply能夠避免一些線程爆炸的情況發(fā)生(創(chuàng)建很多線程)僚匆,具體看下例:

//危險(xiǎn)微渠,可能導(dǎo)致線程爆炸以及死鎖
for (int i = 0; i < 999; i++){
   dispatch_async(q, ^{...});
}
dispatch_barrier_sync(q, ^{});

// 較優(yōu)選擇, GCD 會(huì)管理并發(fā)
dispatch_apply(999, q, ^(size_t i){...});

但需要注意的是dispatch_apply是一個(gè)同步調(diào)用咧擂,會(huì)阻塞當(dāng)前線程逞盆,我們編碼時(shí)應(yīng)實(shí)時(shí)注意不要阻塞主線程。具體dispatch_apply的使用我也寫(xiě)了一個(gè)簡(jiǎn)單的示例如下:

dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        dispatch_apply(1000, queue,  ^(size_t index){
            NSLog(@"====%zu",index);
        });
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"主線程更新");
        });
    });
    NSLog(@"test");

以上結(jié)果會(huì)先輸出test松申,然后循環(huán)1000次輸出"主線程更新"云芦。某些情況下使用dispatch_apply的效果與使用dispatch_group一樣。

5.8 dispatch_once來(lái)執(zhí)行只需運(yùn)行一次的線程安全代碼

單例模式的實(shí)現(xiàn)方法可以用如下同步塊的方法實(shí)現(xiàn):

+ (id)shareInstance {
    static TestClass *shareInstance = nil;
    @synchronized (self) {
        if (!shareInstance) {
            shareInstance = [[TestClass alloc] init];
        }
    }
    return shareInstance;
}

但GCD出現(xiàn)后贸桶,提供了一種更為簡(jiǎn)單的方式:

+ (id)shareInstance {
    static TestClass *shareInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shareInstance = [[TestClass alloc] init];
    });
   return shareInstance;  
}

相對(duì)于同步塊舅逸,dispatch可以簡(jiǎn)化代碼且絕對(duì)的保證線程安全,根本無(wú)需使用重量級(jí)的加鎖皇筛、取鎖機(jī)制低匙。需要注意的是只需執(zhí)行一次的塊來(lái)說(shuō)羽圃,每次調(diào)用函數(shù)傳入的標(biāo)記必須是完全相同的呢蔫,因此開(kāi)發(fā)會(huì)將標(biāo)記變量聲明在static活global作用域里褥影。

5.9 不要使用dispatch_get_current_queue

此函數(shù)已經(jīng)廢棄,只應(yīng)做調(diào)試之用离例。

6 參考資料

終于寫(xiě)到尾聲啦换团,整個(gè)過(guò)程參考了非常多的文章資料,以及代碼示例均經(jīng)過(guò)實(shí)際驗(yàn)證宫蛆。
好文推薦:iOS GCD 死鎖理解
主要參考:iOS多線程--徹底學(xué)會(huì)多線程之『GCD』

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末艘包,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子耀盗,更是在濱河造成了極大的恐慌想虎,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叛拷,死亡現(xiàn)場(chǎng)離奇詭異舌厨,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)忿薇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)裙椭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人署浩,你說(shuō)我怎么就攤上這事揉燃。” “怎么了筋栋?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵炊汤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我弊攘,道長(zhǎng)抢腐,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任肴颊,我火速辦了婚禮氓栈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘婿着。我一直安慰自己授瘦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布竟宋。 她就那樣靜靜地躺著提完,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丘侠。 梳的紋絲不亂的頭發(fā)上徒欣,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音蜗字,去河邊找鬼打肝。 笑死脂新,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的粗梭。 我是一名探鬼主播争便,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼断医!你這毒婦竟也來(lái)了滞乙?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鉴嗤,失蹤者是張志新(化名)和其女友劉穎斩启,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體醉锅,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡兔簇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了荣挨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片男韧。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖默垄,靈堂內(nèi)的尸體忽然破棺而出此虑,到底是詐尸還是另有隱情,我是刑警寧澤口锭,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布朦前,位于F島的核電站,受9級(jí)特大地震影響鹃操,放射性物質(zhì)發(fā)生泄漏韭寸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一荆隘、第九天 我趴在偏房一處隱蔽的房頂上張望恩伺。 院中可真熱鬧,春花似錦椰拒、人聲如沸晶渠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)褒脯。三九已至,卻和暖如春缆毁,著一層夾襖步出監(jiān)牢的瞬間番川,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留颁督,地道東北人践啄。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像沉御,于是被迫代替她去往敵國(guó)和親往核。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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