iOS 多線程 --- GCD

一.進(jìn)程&線程

  • 進(jìn)程:是程序執(zhí)行過程中分配和管理資源的一個(gè)基本單位。
  • 線程:是程序執(zhí)行過程中任務(wù)調(diào)度和執(zhí)行的一個(gè)基本單位。
  • 一個(gè)進(jìn)程里面有多個(gè)線程,線程是進(jìn)程的一部分峡继。

二. 任務(wù)

任務(wù):就是要執(zhí)行什么操作。

1. 同步執(zhí)行(sync):
  • 在當(dāng)前的線程上面執(zhí)行任務(wù)匈挖。
  • 不具備開啟新線程的能力碾牌。
2. 異步執(zhí)行(async):
  • 可以開啟新的線程執(zhí)行任務(wù)。
  • 具備開啟新線程的能力关划。

三. 隊(duì)列

隊(duì)列:用于存放任務(wù)小染,遵循FIFO(先進(jìn)先出)的原則。

1. 串行隊(duì)列(serial):
  • 隊(duì)列中每次只執(zhí)行一個(gè)任務(wù)贮折,當(dāng)?shù)谝粋€(gè)執(zhí)行完才能執(zhí)行第二個(gè)裤翩。
  • 只能開啟一個(gè)線程。
2. 并發(fā)隊(duì)列(concurrent):
  • 隊(duì)列中可以同時(shí)執(zhí)行多個(gè)任務(wù)调榄。
  • 可以開啟多個(gè)線程踊赠。
3. 兩種特殊隊(duì)列:
  • 全局隊(duì)列(dispatch_get_global_queue):直接作為普通并發(fā)隊(duì)列使用。
  • 主隊(duì)列(dispatch_get_main_queue):任務(wù)在主線程中執(zhí)行的串行隊(duì)列每庆。

四. 使用步驟

注:任務(wù)放到...(主隊(duì)列 & 串行隊(duì)列 & 全局隊(duì)列)隊(duì)列中...(同步 & 異步)執(zhí)行筐带。

1. 創(chuàng)建隊(duì)列
  • 串行隊(duì)列 & 并發(fā)隊(duì)列
// 串行隊(duì)列
dispatch_queue_t serialQueue = dispatch_queue_create("com.wxh.queue", DISPATCH_QUEUE_SERIAL);
// 并發(fā)隊(duì)列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.wxh.queue", DISPATCH_QUEUE_CONCURRENT);

注:第一個(gè)參數(shù)表示隊(duì)列的唯一標(biāo)識(shí)符DEBUG的時(shí)候用的缤灵,可以為空伦籍,推薦使用類似于APP的BundleID這種逆序域名;第二個(gè)參數(shù)識(shí)別是串行隊(duì)列還是并發(fā)隊(duì)列腮出,串行隊(duì)列DISPATCH_QUEUE_SERIAL帖鸦,并發(fā)隊(duì)列用DISPATCH_QUEUE_CONCURRENT

  • 主隊(duì)列 & 全局隊(duì)列
// 主隊(duì)列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 全局隊(duì)列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

注:主隊(duì)列不用傳參數(shù)胚嘲。全局隊(duì)列第一個(gè)參數(shù)一般用DISPATCH_QUEUE_PRIORITY_DEFAULT作儿,表示優(yōu)先級(jí)的。第二個(gè)參數(shù)暫時(shí)沒用馋劈,用0即可攻锰。

2. 創(chuàng)建任務(wù)
// 同步任務(wù)創(chuàng)建
dispatch_sync(queue, ^{
});
// 異步任務(wù)創(chuàng)建
dispatch_async(queue, ^{
});

注:參數(shù)queue就是隊(duì)列的類型。

3. 小結(jié)
  • 并發(fā)隊(duì)列全局隊(duì)列使用場(chǎng)景是一致的妓雾,通常都是使用全局隊(duì)列娶吞。
  • 總共有3種隊(duì)列:全局隊(duì)列主隊(duì)列君珠,串行隊(duì)列寝志;有2種任務(wù):同步任務(wù)異步任務(wù)策添。
  • 共有6種使用方式材部,能否開啟新的線程有同步或者 異步決定,但是否開啟新的線程要看當(dāng)前狀況是否需要開啟新的線程來決定唯竹。
  • sync會(huì)照成阻塞現(xiàn)象乐导,sync任務(wù)下的隊(duì)列里面的任務(wù)要必須完成一個(gè)才能繼續(xù)下一個(gè)。
  • sync阻塞的是隊(duì)列浸颓,不是線程物臂。

五. 使用

1. 同步主隊(duì)列 syncMain

解讀:任務(wù)放到主隊(duì)列同步執(zhí)行

dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"任務(wù)1");
    NSLog(@"任務(wù)2");
});
  • 如果在主線程中執(zhí)行syncMain产上,會(huì)出現(xiàn)死鎖現(xiàn)象棵磷。
    解釋:在主線程上面執(zhí)行同步主隊(duì)列syncMain任務(wù),相當(dāng)于把syncMain任務(wù)加到了主隊(duì)列Main中晋涣。當(dāng)要執(zhí)行任務(wù)1的時(shí)候仪媒,也要先把任務(wù)1加到Main中。因?yàn)槭峭饺蝿?wù)sync谢鹊,所以要執(zhí)行任務(wù)1之前算吩,必須先等待syncMain任務(wù)完成。但是要完成syncMain任務(wù)前佃扼,又必須執(zhí)行任務(wù)1偎巢,這時(shí)候syncMain任務(wù)和任務(wù)1之間就會(huì)互相等待,出現(xiàn)死鎖現(xiàn)象兼耀,程序崩潰压昼。

  • 如果是在子線程中使用syncMain,不開啟新的線程瘤运。先完成任務(wù)1窍霞,再完成任務(wù)2
    解釋:因?yàn)槭峭饺蝿?wù)sync尽超,不具備開啟新的線程的能力官撼。在子線程中執(zhí)行syncMain,相當(dāng)于吧syncMain任務(wù)加到了子線程的隊(duì)列(這里用隊(duì)列A表示)中似谁。當(dāng)要執(zhí)行任務(wù)1任務(wù)2的時(shí)候傲绣,把任務(wù)1任務(wù)2加到主隊(duì)列Main中,這個(gè)時(shí)候巩踏,Main中是沒有其他任務(wù)的秃诵,所以不會(huì)出現(xiàn)死鎖現(xiàn)象。Main是特殊的串行隊(duì)列塞琼,所以先執(zhí)行完任務(wù)1菠净,再執(zhí)行任務(wù)2

2. 異步主隊(duì)列 asyncMain

解讀:任務(wù)放到主隊(duì)列異步執(zhí)行

dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"任務(wù)1");
    NSLog(@"任務(wù)2");
});

不開啟新的線程毅往。先完成任務(wù)1牵咙,再完成任務(wù)2
解釋:雖然是異步任務(wù)async攀唯,具備開啟新的線程的能力洁桌。但是由于是在主隊(duì)列Main中執(zhí)行任務(wù),Main中的任務(wù)必須在主線程完成侯嘀,所以不需要開啟新的線程另凌。在主線程中執(zhí)行asyncMain任務(wù),相當(dāng)于把asyncMain任務(wù)加到主隊(duì)列Main中戒幔。當(dāng)要執(zhí)行任務(wù)1的時(shí)候吠谢,也要任務(wù)1加到Main中。因?yàn)槭钱惒饺蝿?wù)async诗茎,所以asyncMain任務(wù)可以先等待工坊,先執(zhí)行完任務(wù)1,再執(zhí)行任務(wù)2错沃,所以不會(huì)出現(xiàn)死鎖現(xiàn)象栅组;在子線程執(zhí)行asyncMain任務(wù),跟子線程執(zhí)行syncMain任務(wù)同等邏輯枢析。

使用場(chǎng)景:做網(wǎng)絡(luò)請(qǐng)求從后臺(tái)接口獲取到數(shù)據(jù)之后玉掸,需要根據(jù)數(shù)據(jù)更新界面UI,一般都是用asyncMain醒叁,在asyncMainblock里面執(zhí)行刷新界面的操作司浪。

3.同步串行隊(duì)列 syncSerial

解讀:任務(wù)放到串行隊(duì)列同步執(zhí)行

dispatch_sync(dispatch_queue_create("com.wxh.queue", DISPATCH_QUEUE_SERIAL), ^{
    NSLog(@"任務(wù)1");
    NSLog(@"任務(wù)2");
});

不會(huì)開啟新的線程把沼。先完成任務(wù)1啊易,再完成任務(wù)2
解釋:因?yàn)槭峭饺蝿?wù)sync饮睬,sync不具備開啟新的線程租谈。執(zhí)行syncSerial任務(wù)時(shí),syncSerial任務(wù)存放在當(dāng)前線程的隊(duì)列(這里用隊(duì)列B表示)中捆愁,執(zhí)行任務(wù)1的時(shí)候割去,將任務(wù)1放到當(dāng)前線程的串行隊(duì)列Serial中,任務(wù)2也一樣昼丑。因?yàn)槭?code>Serial呻逆,所以要先執(zhí)行完任務(wù)1,再執(zhí)行任務(wù)2菩帝。

問題:同步串行隊(duì)列syncSerial在主線程上執(zhí)行咖城,為什么不會(huì)出現(xiàn)死鎖現(xiàn)象茬腿?
回答:在主線程上執(zhí)行syncSerial任務(wù),syncSerial任務(wù)存放在主隊(duì)列Main當(dāng)中宜雀,而任務(wù)1任務(wù)2都放在主線程的串行隊(duì)列Serial中(不是在主隊(duì)列Main中哦~)切平。此時(shí),主線程上面有兩個(gè)隊(duì)列州袒,一個(gè)是存放syncSerial任務(wù)的Main揭绑,另一個(gè)是存放任務(wù)1任務(wù)2Serial弓候。當(dāng)執(zhí)行syncSerial任務(wù)中的任務(wù)1時(shí)郎哭,會(huì)從主隊(duì)列Main去到串行隊(duì)列Serial,然后在Serial繼續(xù)執(zhí)行任務(wù)2菇存,執(zhí)行完任務(wù)2夸研,回到主隊(duì)列Main中完成syncSerial任務(wù)。

4.異步串行隊(duì)列 asyncSerial

解讀:任務(wù)放到串行隊(duì)列異步執(zhí)行依鸥。

dispatch_async(dispatch_queue_create("com.wxh.queue", DISPATCH_QUEUE_SERIAL), ^{
    NSLog(@"任務(wù)1");
    NSLog(@"任務(wù)2");
});

會(huì)開啟一條新的線程亥至。先完成任務(wù)1,再完成任務(wù)2贱迟。
解釋:因?yàn)槭钱惒饺蝿?wù)async姐扮,具備開啟新的線程的能力。因?yàn)槭窃诖嘘?duì)列Serial中衣吠,只能同時(shí)執(zhí)行一個(gè)任務(wù)茶敏,所以只需要開啟一條新的線程。asyncSerial任務(wù)存放在當(dāng)前線程的隊(duì)列中(這里用隊(duì)列C表示)缚俏,而任務(wù)1任務(wù)2存放在新開啟的線程的Serial惊搏。當(dāng)執(zhí)行asyncSerial任務(wù)宝恶,要開始執(zhí)行任務(wù)1時(shí)鞠评,先去到新開啟的線程的Serial中魄衅,執(zhí)行完任務(wù)1,再執(zhí)行任務(wù)2常空,然后回到隊(duì)列C中完成asyncSerial任務(wù)屠阻。

5. 同步全局隊(duì)列 syncGlobal

解讀:任務(wù)放到全局隊(duì)列同步執(zhí)行郁岩。

dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"任務(wù)1");
    NSLog(@"任務(wù)2");
});

不會(huì)開啟新的線程铡恕。先完成任務(wù)1煮嫌,再完成任務(wù)2刹缝。
解釋:因?yàn)槭峭饺蝿?wù)sync碗暗,不具備開啟新的線程能力。雖然是在全局隊(duì)列Global中赞草,可以多個(gè)任務(wù)同時(shí)進(jìn)行讹堤,但是只有一條線程,所以還是要先完成任務(wù)1再執(zhí)行任務(wù)2厨疙。syncGlobal任務(wù)存放在當(dāng)前線程的隊(duì)列(這里用隊(duì)列D表示)中洲守,執(zhí)行任務(wù)1的時(shí)候疑务,將任務(wù)1放到當(dāng)前線程的全局隊(duì)列Global中,任務(wù)2也一樣梗醇。雖然是Global知允,但是只有一條線程,所以要先執(zhí)行完任務(wù)1叙谨,再執(zhí)行任務(wù)2温鸽。

使用場(chǎng)景:上傳多張圖片到后臺(tái),后臺(tái)要求一張一張的上傳手负。

6. 異步全局隊(duì)列 asyncGlobal

解讀:任務(wù)放到全局隊(duì)列異步執(zhí)行涤垫。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"任務(wù)1");
    NSLog(@"任務(wù)2");
});

會(huì)開啟多條新的線程。任務(wù)1任務(wù)2同時(shí)執(zhí)行竟终。
解釋:因?yàn)槭钱惒饺蝿?wù)async蝠猬,async會(huì)具備開啟新的線程能力。因?yàn)槭侨株?duì)列Global统捶,所以Global里面的任務(wù)可以同時(shí)執(zhí)行榆芦。asyncGlobal任務(wù)存放在當(dāng)前線程的隊(duì)列(這里用隊(duì)列E表示)中,而任務(wù)1任務(wù)2存放在各自開啟的線程隊(duì)列中喘鸟。當(dāng)執(zhí)行asyncGlobal任務(wù)匆绣,因?yàn)槭?code>Global,所以任務(wù)1任務(wù)2可以同時(shí)執(zhí)行什黑。

使用場(chǎng)景:上傳多張圖片到后臺(tái)崎淳,可以多張同時(shí)上傳。

六.GCD線程之間的通信

異步開啟子線程執(zhí)行耗時(shí)任務(wù)兑凿,耗時(shí)任務(wù)完成凯力,利用主隊(duì)列回到主線程更新UI。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (int i = 0; i < 10; i ++) {
            NSLog(@"---%d",i);
            // 模擬耗時(shí)操作
            [NSThread sleepForTimeInterval:1];
        }
    });
    dispatch_async(dispatch_get_main_queue(), ^{
        // 耗時(shí)操作完成
        NSLog(@"任務(wù)完成礼华,回到主線程更新UI咐鹤。");
    });
});

七.阻塞方法dispatch_barrier

作用:在有多個(gè)任務(wù)并且使用柵欄方法dispatch_barrier的隊(duì)列(注意:不能使用全局隊(duì)列),必須先等待dispatch_barrier前面的任務(wù)執(zhí)行完畢圣絮,才能執(zhí)行dispatch_barrier里面的任務(wù)祈惶。等待dispatch_barrier里面的任務(wù)執(zhí)行完畢,才能繼續(xù)執(zhí)行dispatch_barrier之后的任務(wù)扮匠。
例子:有三種圖片捧请,分別壓縮之后,一起上傳后后臺(tái)棒搜。

dispatch_queue_t concurrentQueue = dispatch_queue_create("com.wxh.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"壓縮圖片1");
});
dispatch_async(concurrentQueue, ^{
    [NSThread sleepForTimeInterval:1];
    NSLog(@"壓縮圖片2");
});
dispatch_async(concurrentQueue, ^{
    [NSThread sleepForTimeInterval:4];
    NSLog(@"壓縮圖片3");
});
dispatch_barrier_async(concurrentQueue, ^{
    NSLog(@"將 壓縮圖片1 壓縮圖片2 壓縮圖片3 上傳到后臺(tái)");
});
dispatch_async(concurrentQueue, ^{
    NSLog(@"其他操作");
});

結(jié)果:這樣子既能讓3張圖片同時(shí)壓縮疹蛉,又能確保3張圖片都?jí)嚎s完之后,才將3張圖片上傳到后臺(tái)力麸。

問題1:為什么不能使用 全局隊(duì)列 可款?
解釋:蘋果官方給的說明是如果使用全局隊(duì)列育韩,那么dispatch_barrier_async方法將退化成dispatch_async方法。個(gè)人覺得闺鲸,不知道對(duì)不對(duì)筋讨,全局隊(duì)列沒有名字,自定義的并發(fā)隊(duì)列是有名字的摸恍,系統(tǒng)需要重新控制隊(duì)列里面任務(wù)的執(zhí)行操作悉罕,必須具體到哪個(gè)隊(duì)列中去重新控制。

問題2:dispatch_barrier_asyndispatch_barrier_syn的區(qū)別立镶?
解釋:上例中壁袄,將dispatch_barrier_asyn替換成dispatch_barrier_syn效果是一樣的。它們的區(qū)別在于谜慌,

  • dispatch_barrier_asyn將自己的任務(wù)加入到隊(duì)列中之后然想,不用等自己的任務(wù)執(zhí)行完畢,它就將它后面的任務(wù)也加入到隊(duì)列中欣范。然后等待自己的任務(wù)執(zhí)行完畢,才執(zhí)行后面的任務(wù)令哟。
  • dispatch_barrier_syn將自己的任務(wù)加入到隊(duì)列中之后恼琼,需要等待自己的任務(wù)執(zhí)行完畢,才能加入后臺(tái)的任務(wù)屏富,并執(zhí)行后面的任務(wù)晴竞。

八.延時(shí)方法 dispatch_after

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});

方法中需要傳入一個(gè)延時(shí)的時(shí)間(秒),延時(shí)操作里面的任務(wù)放到主隊(duì)列執(zhí)行狠半。

九.只執(zhí)行一次(單例) dispatch_once

+ (instancetype)shareInstance{
    static Singleton *single;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        single = [[Singleton alloc] init];
    });
    return single;
}

在程序運(yùn)行過程中噩死,dispatch_once方法中的代碼只會(huì)被執(zhí)行1次,即不影響性能神年,又能保證線程安全已维。

  • 原理:dispatch_once方法是根據(jù)dispatch_once_t修飾的變量onceToken的值來決定接下來的操作的。
    (1)當(dāng)onceToken = 0時(shí)已日,說明程序第一次執(zhí)行dispatch_once方法垛耳,直接執(zhí)行dispatch_onceblock中的代碼。
    (2)當(dāng)onceToken = -1時(shí)飘千,說明程序已經(jīng)執(zhí)行完過dispatch_once方法堂鲜,那么跳過dispatch_onceblock的代碼,執(zhí)行block之后的代碼护奈。
    (3)當(dāng)onceToken != 0onceToken != -1時(shí)缔莲,說明現(xiàn)在有線程(這里用線程A表示)在執(zhí)行dispatch_once方法,但是還沒執(zhí)行完畢霉旗。這個(gè)時(shí)候痴奏,當(dāng)前這條線程處于阻塞狀態(tài)磺箕,等待線程A執(zhí)行完畢。當(dāng)線程A執(zhí)行完dispatch_once方法時(shí)抛虫,onceToken的值會(huì)變成-1,這時(shí)候當(dāng)前這條線程繼續(xù)執(zhí)行松靡。
    (4)單例可以看成是一種特殊的實(shí)例,是一個(gè)全局的對(duì)象建椰,有且只有一個(gè)的對(duì)象雕欺。

十.快速迭代 dispatch_apply

NSLog(@"---");
dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
    // 模擬耗時(shí)操作
    [NSThread sleepForTimeInterval:(11 - index)];
    NSLog(@"%zu",index);
});
NSLog(@"+++");
 ---
 3
 2
 1
 0
 6
 5
 4
 7
 9
 8
 +++

dispatch_apply是一個(gè)快速迭代的方法,類似于for循環(huán)棉姐。

  • 如果方法中傳入的是一個(gè)串行隊(duì)列屠列,那么dispatch_apply里面的耗時(shí)操作就需要按順序同步執(zhí)行(相當(dāng)于異步串行隊(duì)列,必須執(zhí)行完成一個(gè)任務(wù)之后伞矩,才能執(zhí)行下一個(gè)任務(wù)笛洛。不過一般不會(huì)這么做,這樣操作就失去了快速迭代的意義)乃坤。
  • 如果方法中傳入的是一個(gè)全局隊(duì)列苛让,那么里面多個(gè)耗時(shí)操作就可以同時(shí)進(jìn)行(相當(dāng)于異步并發(fā)隊(duì)列,可以多個(gè)任務(wù)同時(shí)執(zhí)行)。
  • 無論傳入的是串行隊(duì)列還是全局隊(duì)列湿诊,dispatch_apply方法都會(huì)阻塞當(dāng)前線程等待所有任務(wù)執(zhí)行完畢狱杰,才能執(zhí)行dispatch_apply方法后面的代碼(相當(dāng)于同步任務(wù))。

十一. 隊(duì)列組 dispatch_group

需求:在填寫個(gè)人資料頁(yè)面厅须,我們需要把個(gè)人的信息(名字仿畸,手機(jī)號(hào)等)上傳到后臺(tái),也需要把照片(身份證正反面拍照等)也上傳到后臺(tái)朗和,需要做兩個(gè)網(wǎng)絡(luò)請(qǐng)求错沽。當(dāng)兩個(gè)網(wǎng)絡(luò)請(qǐng)求都成功回調(diào)之后,返回上一個(gè)頁(yè)面眶拉。

1. 第一種方法:使用dispatch_group_notify監(jiān)聽千埃。

NSLog(@"---");
dispatch_group_t group =  dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for (int i = 0; i < 2; ++i) {
        [NSThread sleepForTimeInterval:3];
        NSLog(@"上傳個(gè)人信息");
    }
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for (int i = 0; i < 3; ++i) {
        [NSThread sleepForTimeInterval:1];
        NSLog(@"上傳圖片資料");
    }
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"返回上個(gè)界面");
});
NSLog(@"===");
---
===
上傳圖片資料
上傳圖片資料
上傳個(gè)人信息
上傳圖片資料
上傳個(gè)人信息
返回上個(gè)界面
  • dispatch_group_create()創(chuàng)建一個(gè)隊(duì)列組。
  • dispatch_group_async將任務(wù)放到隊(duì)列里面镀层,然后再講隊(duì)列放到隊(duì)列組里面镰禾。
  • dispatch_group_notify監(jiān)聽隊(duì)列組中其他隊(duì)列的任務(wù)完成狀態(tài),當(dāng)所有的任務(wù)都執(zhí)行完成之后唱逢,將自身block里面的任務(wù)也方法隊(duì)列組中吴侦,執(zhí)行任務(wù)。
  • dispatch_group_notify不會(huì)阻塞當(dāng)前線程坞古。

2. 第二種方法:使用dispatch_group_wait阻塞當(dāng)前線程备韧。

NSLog(@"---");
dispatch_group_t group =  dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for (int i = 0; i < 2; ++i) {
        [NSThread sleepForTimeInterval:3];
        NSLog(@"上傳個(gè)人信息");
    }
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for (int i = 0; i < 3; ++i) {
        [NSThread sleepForTimeInterval:1];
        NSLog(@"上傳圖片資料");
    }
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"返回上個(gè)界面");
NSLog(@"===");
---
上傳圖片資料
上傳圖片資料
上傳個(gè)人信息
上傳圖片資料
上傳個(gè)人信息
返回上個(gè)界面
===
  • 當(dāng)所有任務(wù)都完成之后,才執(zhí)行dispatch_group_wait后面的任務(wù)痪枫。
  • dispatch_group_wait會(huì)阻塞當(dāng)前的線程织堂。

3. 第三種方法:使用dispatch_group_enterdispatch_group_leave組合代替dispatch_group_async叠艳。

NSLog(@"---");
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, ^{
    for (int i = 0; i < 2; ++i) {
        [NSThread sleepForTimeInterval:3];
        NSLog(@"上傳個(gè)人信息");
    }
    dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_async(queue, ^{
    for (int i = 0; i < 3; ++i) {
        [NSThread sleepForTimeInterval:1];
        NSLog(@"上傳圖片資料");
    }
    dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"返回上個(gè)界面");
});
NSLog(@"===");
---
===
上傳圖片資料
上傳圖片資料
上傳個(gè)人信息
上傳圖片資料
上傳個(gè)人信息
返回上個(gè)界面
  • dispatch_group_enter表示把一個(gè)任務(wù)放到隊(duì)列組group,并開始執(zhí)行易阳。
  • dispatch_group_leave 表示任務(wù)執(zhí)行完畢附较。
  • dispatch_group_enter + dispatch_group_leave = dispatch_group_async
  • 可以用dispatch_group_notify,同樣也可以用dispatch_group_wait(結(jié)果跟第2種方法一樣)潦俺。區(qū)別是后者會(huì)照成當(dāng)前線程阻塞拒课,前者不會(huì)。
  • 當(dāng)group中的所有任務(wù)都執(zhí)行完畢時(shí)事示,才會(huì)執(zhí)行dispatch_group_wait后面的任務(wù)早像,或者執(zhí)行追加到dispatch_group_notify中的任務(wù)。

十二. 信號(hào)量 dispatch_semaphore

三個(gè)重要方法

  • dispatch_semaphore_create:創(chuàng)建并初始化一個(gè)信號(hào)總量肖爵,一般為0或者1卢鹦。
  • dispatch_semaphore_signal:發(fā)送一個(gè)信號(hào),即讓信號(hào)總量+1劝堪。
  • dispatch_semaphore_wait:如果當(dāng)前信號(hào)總量為0冀自,那么阻塞當(dāng)前線程,否則幅聘。信號(hào)總量-1凡纳,正常執(zhí)行。

1. 異步線程變成同步帝蒿。

需求:有時(shí)候需要實(shí)時(shí)拿到異步里面耗時(shí)操作的結(jié)果,才能正確的執(zhí)行之后的代碼巷怜。

__block NSInteger i = 1;
// 創(chuàng)建一個(gè)信號(hào)總量為`0`的信號(hào)量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [NSThread sleepForTimeInterval:2];
    i ++;
    dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%ld",(long)i);

打印結(jié)果為:2
解釋:當(dāng)?shù)谝淮螆?zhí)行dispatch_semaphore_wait時(shí)葛超,信號(hào)總量為0,當(dāng)前線程阻塞延塑。當(dāng)執(zhí)行完異步block里面的耗時(shí)操作之后绣张,執(zhí)行了dispatch_semaphore_signal,信號(hào)總量+1关带。從block里面出來第二次執(zhí)行dispatch_semaphore_wait時(shí)侥涵,信號(hào)總量為1,正常執(zhí)行宋雏。這樣就能等到異步執(zhí)行完之后芜飘,再執(zhí)行接下來的代碼(類似于同步執(zhí)行)。

2. 線程安全(線程鎖)

需求:有時(shí)候磨总,我們會(huì)在多個(gè)地方同時(shí)對(duì)同一個(gè)接口進(jìn)行調(diào)用嗦明,那如果每次調(diào)用過程會(huì)對(duì)下一次調(diào)用的結(jié)果有影響(有修改或者更變等操作),那么我們就必須保證該接口同一時(shí)間只能被一個(gè)地方調(diào)用蚪燕,這就是線程安全娶牌。

- (void)viewDidLoad {
    [super viewDidLoad];
    self.semaphore = dispatch_semaphore_create(1);
}
- (void)tiaoyongjiekou{
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 模擬耗時(shí)操作
        [NSThread sleepForTimeInterval:1.0];
        dispatch_semaphore_signal(weakSelf.semaphore);
    });
}

解釋:在外部創(chuàng)建一個(gè)信號(hào)總量為1的信號(hào)量奔浅,當(dāng)?shù)谝淮握{(diào)用tiaoyongjiekou方法,執(zhí)行到dispatch_semaphore_wait時(shí)诗良,因?yàn)楫?dāng)前信號(hào)總量為1汹桦,那么正常執(zhí)行并且信號(hào)總量-1(此時(shí)信號(hào)總量為0)。如果第一次調(diào)用還沒執(zhí)行完成鉴裹,第二次就開始調(diào)用舞骆,當(dāng)執(zhí)行到dispatch_semaphore_wait時(shí),信號(hào)總量為0壹罚,線程阻塞葛作,只能原地等待。等第一次調(diào)用結(jié)束猖凛,執(zhí)行完耗時(shí)操作之后赂蠢,執(zhí)行了dispatch_semaphore_signal,信號(hào)總量+1(此時(shí)信號(hào)總量為1)辨泳,那么第二次調(diào)用才能繼續(xù)執(zhí)行虱岂。這樣就能確保同一時(shí)間只被調(diào)用一次,確保線安全菠红。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末第岖,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子试溯,更是在濱河造成了極大的恐慌蔑滓,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遇绞,死亡現(xiàn)場(chǎng)離奇詭異键袱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)摹闽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蹄咖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人付鹿,你說我怎么就攤上這事澜汤。” “怎么了舵匾?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵俊抵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我纽匙,道長(zhǎng)务蝠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮馏段,結(jié)果婚禮上轩拨,老公的妹妹穿的比我還像新娘。我一直安慰自己院喜,他們只是感情好亡蓉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著喷舀,像睡著了一般砍濒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上硫麻,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天爸邢,我揣著相機(jī)與錄音,去河邊找鬼拿愧。 笑死杠河,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的浇辜。 我是一名探鬼主播券敌,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼柳洋!你這毒婦竟也來了待诅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤熊镣,失蹤者是張志新(化名)和其女友劉穎卑雁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绪囱,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡序厉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了毕箍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡道盏,死狀恐怖而柑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情荷逞,我是刑警寧澤媒咳,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站种远,受9級(jí)特大地震影響涩澡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坠敷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一妙同、第九天 我趴在偏房一處隱蔽的房頂上張望射富。 院中可真熱鬧,春花似錦粥帚、人聲如沸胰耗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)柴灯。三九已至,卻和暖如春费尽,著一層夾襖步出監(jiān)牢的瞬間赠群,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工旱幼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留查描,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓速警,卻偏偏與公主長(zhǎng)得像叹誉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闷旧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

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