【原創(chuàng)】iOS 多線程之GCD 及GCD API的使用

原創(chuàng)承边,轉(zhuǎn)載請注明出處聊训。

拋磚引玉彤悔。最近在復(fù)習(xí)了《Obj-C高級編程》這本書后乡翅,一方面記錄一下知識點,另一方便加了一些自己的理解瞬浓。結(jié)合一些經(jīng)典的例子以及實際使用場景加深理解旋讹,權(quán)當(dāng)學(xué)習(xí)交流之用序厉。

需要了解的基本概念

1.同步執(zhí)行:阻塞當(dāng)前線程邻辉。
2.異步執(zhí)行:不阻塞當(dāng)前線程溪王。
3.串行隊列:按照FIFO原則出列腮鞍,一個一個的執(zhí)行。
4.并行隊列: 一起執(zhí)行莹菱。
后續(xù)內(nèi)容會再做解釋移国。

基礎(chǔ)API

dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

這是gcd 中最常見的兩個API,其中:

參數(shù)部分
  1. 第一個參數(shù) dispatch_queue_t代表放在哪個隊列執(zhí)行道伟,系統(tǒng)提供了幾種隊列:
  • dispatch_get_global_queue(long identifier, unsigned long flags)全局并行隊列桥狡。第一個參數(shù)表示優(yōu)先級,最后一個參數(shù)寫0就可以了皱卓。系統(tǒng)提供了四種優(yōu)先級:(優(yōu)先級由高到低)
 *  - DISPATCH_QUEUE_PRIORITY_HIGH:         QOS_CLASS_USER_INITIATED
 *  - DISPATCH_QUEUE_PRIORITY_DEFAULT:      QOS_CLASS_DEFAULT
 *  - DISPATCH_QUEUE_PRIORITY_LOW:          QOS_CLASS_UTILITY
 *  - DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND

一般dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)這么寫就行了。

  • dispatch_get_main_queue()主隊列部逮,也就是主線程的執(zhí)行隊列娜汁。此為串行隊列。按照FIFO原則執(zhí)行兄朋。
  • 同樣我們也可以自定義隊列:dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
    第一個參數(shù)為隊列的名稱掐禁,《Obj-C高級編程》作者推薦使用應(yīng)用ID這種逆序全程域名的命名方式:"com.example.gcd.MyConcurrentDispatchQueue",該名稱會出現(xiàn)在程序崩潰的crashlog中颅和。便于排查問題傅事。
    第二個參數(shù)代表隊列類型,NULLDISPATCH_QUEUE_SERIAL創(chuàng)建串行隊列峡扩。DISPATCH_QUEUE_CONCURRENT創(chuàng)建并行隊列蹭越。
  1. 第二個參數(shù)dispatch_block_t 是一個block,我們把需要使用GCD執(zhí)行的任務(wù)放在這個block里教届。
函數(shù)部分

dispatch_sync:代表同步執(zhí)行响鹃,對應(yīng)基礎(chǔ)概念里的同步執(zhí)行。
這個方法會阻塞當(dāng)前線程案训,將第二個參數(shù)block里的任務(wù)追加到第一個參數(shù)指定的隊列queue里執(zhí)行买置。直到block里的任務(wù)執(zhí)行完畢,程序才繼續(xù)往下運行强霎。
假如當(dāng)前線程的執(zhí)行隊列和第一個參數(shù)里的queue是同一個隊列忿项,且都是串行隊列,那么就會造成死鎖城舞。(見代碼1.1.1,1.1.2)

dispatch_async:代表異步執(zhí)行轩触。不阻塞當(dāng)前線程,即使用多個線程同時執(zhí)行多個處理椿争。其中異步執(zhí)行一個并行隊列怕膛,XNU內(nèi)核會基于Dispatch Queue中的處理數(shù)、CPU核數(shù)以及CPU負荷等當(dāng)前系統(tǒng)的狀態(tài)來決定要不要開辟新的線程秦踪,以及開辟多少個線程來處理褐捻。(代碼1.2)

代碼 1.1.1 死鎖

當(dāng)前線程為主線程掸茅,當(dāng)前隊列為主隊列。queue內(nèi)參數(shù)也為主隊列柠逞,同時他們都是串行隊列昧狮。那么按照剛剛我們總結(jié)的簡單理論,發(fā)生死鎖板壮。

     NSLog(@"程序開始運行");
    //主線程阻塞,開始執(zhí)行block里的任務(wù)
    dispatch_sync(dispatch_get_main_queue(), ^{
        //task2
        NSLog(@"此句不執(zhí)行,加到主隊列中執(zhí)行,排在task3后面逗鸣,按照FIFO原則需要等待 task3執(zhí)行完畢才能執(zhí)行");
    });
    // task2 等待task3, task3 等待 task2 .死鎖
    NSLog(@"此句不執(zhí)行,主線程主隊列死鎖");

我們將dispatch_get_main_queue 替換為dispatch_get_global_queue使得二者不為同一個串行隊列,則不會發(fā)生死鎖绰精。同學(xué)們可以自行試驗撒璧。

代碼 1.1.2 死鎖 。

當(dāng)前執(zhí)行隊列和dispatch_syncqueue參數(shù)都為同一個同步隊列笨使。發(fā)生死鎖卿樱。

    NSLog(@"task1");
    dispatch_queue_t otherQueue = dispatch_queue_create("com.test.gcd.serialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(otherQueue, ^{
        NSLog(@"task2");
        //發(fā)生死鎖,當(dāng)前執(zhí)行隊列和dispatch_sync 執(zhí)行隊列相同且都是同步隊列
        dispatch_sync(otherQueue, ^{
            NSLog(@"task4");
            //task4 排在otherQueue執(zhí)行任務(wù)task5之后硫椰,需要task5執(zhí)行完畢才可以執(zhí)行繁调。
        });
        //同步執(zhí)行,需要task4 執(zhí)行完畢才可以執(zhí)行task5靶草。
        //task4 等待task5, task5 等待task4,發(fā)生死鎖蹄胰。
        NSLog(@"task5");
    });
    //打印 task1 task2 task3(task2,task3順序不定)
    NSLog(@"task3");

同樣我們可以將dispatch_sync 里的otherQueue替換為任意一個非相同隊列,則不會發(fā)生死鎖奕翔。這里也不再贅述裕寨。

代碼 1.2 是否開啟新線程,以及開辟多少個。
    dispatch_queue_t queue1 = dispatch_queue_create("com.test.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue2 = dispatch_queue_create("com.test.gcd.concurrentQueue2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue1, ^{
        NSLog(@"thread%@",[NSThread currentThread]);
        dispatch_async(queue2, ^{
            NSLog(@"thread%@",[NSThread currentThread]);
        });
    });
    //有時打印
    //thread<NSThread: 0x6000037a1f40>{number = 4, name = (null)}
    //thread<NSThread: 0x6000037a1f40>{number = 4, name = (null)}
    //有時打印
    //thread<NSThread: 0x6000037a1f40>{number = 4, name = (null)}
    //thread<NSThread: 0x600003751e40>{number = 6, name = (null)}

以上試驗也可以驗證了這一理論糠悯,有時只需要開辟一個線程即可處理帮坚。有時需要開辟兩個新線程。

iOS和OS X的核心--XNU內(nèi)核決定應(yīng)當(dāng)使用的線程數(shù)互艾,并只生成所需的線程執(zhí)行處理试和。另外,當(dāng)處理結(jié)束纫普,應(yīng)當(dāng)執(zhí)行的處理數(shù)減少時阅悍,XNU內(nèi)核會結(jié)束不再需要的線程。XNU內(nèi)核僅使用Concurrent Dispatch Queue便可完美地管理并行執(zhí)行多個處理的線程昨稼。

dispatch_set_target_queue

dispatch_set_target_queue(dispatch_object_t object,dispatch_queue_t _Nullable queue);
此API节视,可以改變執(zhí)行隊列優(yōu)先級以及隊列類型。

  • Concurrent Dispatch Queue 改 Serial Dispatch Queue :
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t serialQueue = dispatch_queue_create("com.test.gcd.serialQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_set_target_queue(concurrentQueue, serialQueue);
    dispatch_async(concurrentQueue, ^{
        NSLog(@"task1 thread:%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"task2 thread:%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"task3 thread:%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"task4 thread:%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"task5 thread:%@",[NSThread currentThread]);
    });

注釋掉 dispatch_set_target_queue這行假栓,會無序打印task1-5寻行。
加上后實際上執(zhí)行隊列由并行變成了串行執(zhí)行,task1-5按順序打印匾荆。

  • 改變隊列優(yōu)先級
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    
    dispatch_set_target_queue(concurrentQueue, globalQueue);

dispatch_queue_create 函數(shù)生成的Dispatch Queue 不管是Serial Queue 還是 Concurrent Queue拌蜘,都使用與默認優(yōu)先級Global Dispatch Queue相同執(zhí)行優(yōu)先級的線程杆烁。而變更生成的Dispatch Queue 的執(zhí)行優(yōu)先級要使用dispatch_set_target_queue函數(shù)。

dispatch_after

dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
參數(shù):
1.時間(指定時間追加處理到Dispatch Queue)简卧。
2.隊列兔魂。
3.任務(wù)。

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull *NSEC_PER_SEC);
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"3秒后追加到主線程隊列里執(zhí)行");
    });

因為主線程主隊列举娩,在主線程RunLoop中執(zhí)行析校,所以在每隔1/60秒執(zhí)行的RunLoop中,任務(wù)最快在3秒后執(zhí)行铜涉,最慢在3+1/60秒后執(zhí)行智玻。如果主隊列有大量處理,那么這個時間會更長芙代。

Dispatch Group

在多個并行執(zhí)行的任務(wù)全部執(zhí)行完畢后尚困,想要追加一個結(jié)束處理。這種場景往往比較常見链蕊。雖然可以通過別的方式實現(xiàn),但邏輯會變的復(fù)雜谬泌,代碼也不雅觀滔韵。這時候Dispatch Group就發(fā)揮作用了。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"task1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"task2");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"task3");
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"task Done");
    });
    //打印 (task1-3 無序)
    //task1
    //task2
    //task3
    //task Done

一個簡單的demo掌实,在任務(wù)1-3完成后陪蜻,執(zhí)行task Done

除了使用dispatch_group_notifyAPI 處理group任務(wù)結(jié)束外,還可以使用dispatch_group_wait函數(shù)贱鼻。同樣的例子:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"task1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"task2");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"task3");
    });
    
    //也可以使用dispatch_group_wait 函數(shù)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"task Done");

DISPATCH_TIME_FOREVER代表永久等待宴卖。

我們也可以指定等待的時間,下例等待1秒邻悬,超過1秒不再等待症昏。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"task1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"task2");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"task3");
        for (int i = 0; i < 10000000; i++){
            @autoreleasepool{
                NSString* string = @"ab c";
                //生成autorelease對象
                NSArray* array = [string componentsSeparatedByString:string];
            }
        }
    });
    
    //DISPATCH_TIME_FOREVER 永久等待,同樣我們可以設(shè)置等待的時間
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);
    long result = dispatch_group_wait(group, time);
    if (result == 0) {
        // 屬于Dispatch Group 的全部處理執(zhí)行結(jié)束
        NSLog(@"task Done");
    } else {
        // 屬于Dispatch Group 的某個處理還在執(zhí)行中
        NSLog(@"task Doing");
    }

通過dispatch_group_wait返回值可以判斷父丰,是group任務(wù)在設(shè)置的超時時間內(nèi)完成肝谭,還是超時未完成。 result ==0 代表全部處理完成蛾扇,非0代表執(zhí)行超時了攘烛。

但假如Dispatch Queue 里的任務(wù)是一個個網(wǎng)絡(luò)請求的話,由于網(wǎng)絡(luò)請求是異步執(zhí)行镀首,那么實際達不到我們想要的在所有請求完畢后執(zhí)行某段代碼的目的坟漱。那么這時就可以借助信號量Dispatch Semaphore來完成了。

Dispatch Semaphore

  • dispatch_semaphore_create(long value) 創(chuàng)建一個信號量更哄。
  • dispatch_semaphore_signal(dispatch_semaphore_t dsema) 信號量加1
  • dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout) 等待信號大0時執(zhí)行芋齿,并對信號量進行減一操作腥寇。
1.上述在Dispatch Queue里并行執(zhí)行多個網(wǎng)絡(luò)請求的情況,想要在所有請求都完成的情況執(zhí)行某段代碼就可以使用Dispatch Semaphore了沟突。
- (void)requestDemo{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    __weak typeof(self) weak_self = self;
    dispatch_group_async(group, queue, ^{
        NSLog(@"請求任務(wù)A");
        [weak_self requestA];
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"請求任務(wù)B");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"請求任務(wù)C");
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"所有請求完成");
    });
}

- (void)requestA{
    //    用于GCD Group 以及 NSOperationQueue中設(shè)置依賴關(guān)系的任務(wù)花颗,因為網(wǎng)絡(luò)請求異步執(zhí)行,
    //    不會阻塞當(dāng)前線程惠拭,達不到按序執(zhí)行的效果扩劝。
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    //    [異步請求:{
    //        成功: dispatch_semaphore_signal(sema);
    //        失敗: dispatch_semaphore_signal(sema);
    //    }];
    //一直等待到信號量大于0才執(zhí)行,并減1
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}

異步請求方法前創(chuàng)建為0的信號量职辅,請求結(jié)束后信號量+1棒呛,dispatch_semaphore_wait會等到信號量大于才繼續(xù)運行。整個請求模塊會在dispatch_semaphore_wait可以繼續(xù)運行才標記為block任務(wù)結(jié)束域携。

2.控制異步執(zhí)行Dispatch Concurrent Queue最大并發(fā)數(shù)簇秒。

眾所周知NSOperationQueue便于管理多線程,可以設(shè)置maxConcurrentOperationCount來控制多線程執(zhí)行的最大并發(fā)數(shù)秀鞭。那么GCD要如何控制最大并發(fā)呢趋观?這時Dispatch Semaphore又發(fā)揮作用了。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //初始信號量1 锋边,這里1可以為n
    dispatch_semaphore_t sema = dispatch_semaphore_create(1);
    for (NSInteger i = 0; i < 10; i++) {
        //大于0執(zhí)行皱坛,并減1
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        dispatch_async(queue, ^{
            NSLog(@"%ld",i);
            //任務(wù)完成,信號量加1
            dispatch_semaphore_signal(sema);
        });
    }
    //按順序打印0-9

通過設(shè)置dispatch_semaphore_create (1)設(shè)置最大并發(fā)1豆巨,那么實際上就把并發(fā)隊列設(shè)置成了一個串行隊列剩辟。dispatch_semaphore_create (n)則最大并發(fā)為n,如果n設(shè)置的很大往扔,實際上達不到n贩猎。因為蘋果內(nèi)核決定了此次GCD執(zhí)行的并發(fā)隊列所需要的線程數(shù)。

未完待續(xù)萍膛。吭服。。
后續(xù)補充:

dispatch_barrier_async
dispatch_apply
dispatch_once

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蝗罗,一起剝皮案震驚了整個濱河市噪馏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绿饵,老刑警劉巖欠肾,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拟赊,居然都是意外死亡刺桃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進店門吸祟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瑟慈,“玉大人桃移,你說我怎么就攤上這事「鸨蹋” “怎么了借杰?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵,是天一觀的道長进泼。 經(jīng)常有香客問我蔗衡,道長,這世上最難降的妖魔是什么乳绕? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任绞惦,我火速辦了婚禮,結(jié)果婚禮上洋措,老公的妹妹穿的比我還像新娘济蝉。我一直安慰自己,他們只是感情好菠发,可當(dāng)我...
    茶點故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布王滤。 她就那樣靜靜地躺著,像睡著了一般滓鸠。 火紅的嫁衣襯著肌膚如雪淑仆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天哥力,我揣著相機與錄音,去河邊找鬼墩弯。 笑死吩跋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的渔工。 我是一名探鬼主播锌钮,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼引矩!你這毒婦竟也來了梁丘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤旺韭,失蹤者是張志新(化名)和其女友劉穎氛谜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體区端,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡值漫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了织盼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杨何。...
    茶點故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡酱塔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出危虱,到底是詐尸還是另有隱情羊娃,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布埃跷,位于F島的核電站蕊玷,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捌蚊。R本人自食惡果不足惜集畅,卻給世界環(huán)境...
    茶點故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缅糟。 院中可真熱鬧挺智,春花似錦、人聲如沸窗宦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赴涵。三九已至媒怯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間髓窜,已是汗流浹背扇苞。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留寄纵,地道東北人鳖敷。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像程拭,于是被迫代替她去往敵國和親定踱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,922評論 2 361

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