iOS多線程實現(xiàn)——GCD使用詳解

一继效、介紹

GCD早像,英文全稱是Grand Central Dispatch(功能強悍的中央調(diào)度器)桨踪,基于C語言編寫的一套多線程開發(fā)機制讥巡,因此使用時會以函數(shù)形式出現(xiàn)掀亩,且大部分函數(shù)以dispatch開頭,雖然是C語言的但相對于蘋果其它多線程實現(xiàn)方式欢顷,抽象層次更高槽棍,使用起來也更加方便。

它是蘋果為應(yīng)對多核的并行運算提出的解決方案抬驴,它會自動利用多核進(jìn)行并發(fā)處理和運算刹泄,它能提供系統(tǒng)級別的處理,而不再局限于某個進(jìn)程怎爵、線程特石,官方聲稱會更快、更高效鳖链、更靈敏姆蘸,且線程由系統(tǒng)自動管理(調(diào)度墩莫、運行),無需程序員參與逞敷,使用起來非常方便狂秦。

二、任務(wù)和隊列

GCD有兩個核心:任務(wù)和隊列推捐。

任務(wù):要執(zhí)行的操作或方法函數(shù)裂问,隊列:存放任務(wù)的集合,而我們要做的就是將任務(wù)添加到隊列然后執(zhí)行牛柒,GCD會自動將隊列中的任務(wù)按先進(jìn)先出的方式取出并交給對應(yīng)線程執(zhí)行堪簿。注意任務(wù)的取出是按照先進(jìn)先出的方式,這也是隊列的特性皮壁,但是取出后的執(zhí)行順序則不一定椭更,下面會詳細(xì)討論。

1 任務(wù)

任務(wù)是一個比較抽象的概念蛾魄,可以簡單的認(rèn)為是一個操作虑瀑、一個函數(shù)、一個方法等等滴须,在實際的開發(fā)中大多是以block(block使用詳見)的形式舌狗,使用起來也更加靈活。

2 隊列queue

  • 有兩種隊列:串行隊列和并行隊列扔水。串行隊列:同步執(zhí)行把夸,在當(dāng)前線程執(zhí)行;并行隊列:可由多個線程異步執(zhí)行铭污,但任務(wù)的取出還是FIFO的

隊列創(chuàng)建恋日,根據(jù)函數(shù)第二個參數(shù)來創(chuàng)建串行或并行隊列。

// 參數(shù)1 隊列名稱
// 參數(shù)2 隊列類型 DISPATCH_QUEUE_SERIAL/NULL串行隊列嘹狞,DISPATCH_QUEUE_CONCURRENT代表并行隊列
// 下面代碼為創(chuàng)建一個串行隊列岂膳,也是實際開發(fā)中用的最多的
dispatch_queue_t serialQ = dispatch_queue_create("隊列名", NULL);
  • 另外系統(tǒng)提供了兩種隊列:全局隊列和主隊列。

全局隊列屬于并行隊列磅网,只不過已由系統(tǒng)創(chuàng)建的沒有名字谈截,且在全局可見(可用)。獲取全局隊列:

/* 取得全局隊列
 第一個參數(shù):線程優(yōu)先級,設(shè)為默認(rèn)即可涧偷,個人習(xí)慣寫0簸喂,等同于默認(rèn)
 第二個參數(shù):標(biāo)記參數(shù),目前沒有用燎潮,一般傳入0
 */
serialQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

主隊列屬于串行隊列喻鳄,也由系統(tǒng)創(chuàng)建,只不過運行在主線程(UI線程)确封。獲取主隊列:

// 獲取主隊列
serialQ = dispatch_get_main_queue();
  • 關(guān)于內(nèi)存:queue屬于一個對象除呵,也是占用內(nèi)存的再菊,也會使用引用計數(shù),當(dāng)向queue添加一個任務(wù)時就會將這個queue retain一下颜曾,引用計數(shù)+1纠拔,直到所有任務(wù)都完成內(nèi)存才會釋放。(我們在聲明一個queue屬性時要用strong)泛豪。

3 執(zhí)行方式——2種

同步執(zhí)行和異步執(zhí)行稠诲。

  • 同步執(zhí)行:不會開啟新的線程,在當(dāng)前線程執(zhí)行诡曙。
  • 異步執(zhí)行:gcd管理的線程池中有空閑線程就會從隊列中取出任務(wù)執(zhí)行臀叙,會開啟線程。

下面為實現(xiàn)同步和異步的函數(shù)岗仑,函數(shù)功能為:將任務(wù)添加到隊列并執(zhí)行。

/* 同步執(zhí)行
 第一個參數(shù):執(zhí)行任務(wù)的隊列:串行聚请、并行荠雕、全局、主隊列
 第二個參數(shù):block任務(wù)
 */
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// 異步執(zhí)行
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

注意:默認(rèn)情況下驶赏,新線程都沒有開啟runloop炸卑,所以當(dāng)block任務(wù)完成后,線程都會自動被回收煤傍,假設(shè)我們想在新開的線程中使用NSTimer盖文,就必須開啟runloop,可以使用[[NSRunLoop currentRunLoop] run]開啟當(dāng)前線程蚯姆,這是就要自己管理線程的回收等工作五续。

  • 另外還有兩個方法,實際開發(fā)中用的并不是太多

dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);

dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

加了一個barrier龄恋,意義在于:隊列之前的block處理完成之后才開始處理隊列中barrier的block疙驾,且barrier的block必須處理完之后,才能處理其它的block郭毕。

根據(jù)這個特性我們可以實現(xiàn)123456一共6個block它碎,可以讓特定幾個并發(fā)執(zhí)行完成之后,再并發(fā)執(zhí)行剩下的block显押。比如123先并發(fā)扳肛,之后456再并發(fā)執(zhí)行。具體代碼如下(將barrier放在123與456之間即可):

- (void)barrierTest {
    // 1 創(chuàng)建并發(fā)隊列
    dispatch_queue_t BCqueue = dispatch_queue_create("BarrierConcurrent", DISPATCH_QUEUE_CONCURRENT);
    
    // 2.1 添加任務(wù)123
    dispatch_async(BCqueue, ^{
        NSLog(@"task1,%@", [NSThread currentThread]);
    });
    dispatch_async(BCqueue, ^{
        sleep(3);
        NSLog(@"task2,%@", [NSThread currentThread]);
    });
    dispatch_async(BCqueue, ^{
        sleep(1);
        NSLog(@"task3,%@", [NSThread currentThread]);
    });
    // 2.2 添加barrier
    dispatch_barrier_async(BCqueue, ^{
        NSLog(@"barrier");
    });
    // 2.3 添加任務(wù)456
    dispatch_async(BCqueue, ^{
        sleep(1);
        NSLog(@"task4,%@", [NSThread currentThread]);
    });
    dispatch_async(BCqueue, ^{
        NSLog(@"task5,%@", [NSThread currentThread]);
    });
    dispatch_async(BCqueue, ^{
        NSLog(@"task6,%@", [NSThread currentThread]);
    });
}

輸出結(jié)果乘碑,為了顯示效果挖息,代碼有延時操作:

gcdBarrierTest.png

三、幾種類型

很明顯兩種執(zhí)行方式兽肤,兩種隊列旋讹。那么就有4種情況:串行隊列同步執(zhí)行殖蚕、串行隊列異步執(zhí)行、并行隊列同步執(zhí)行沉迹、并行隊列異步執(zhí)行睦疫。哪一種會開啟新的線程?開幾條鞭呕?是否并發(fā)蛤育?記憶起來比較繞,但是只要抓住基本的就可以葫松,為了方便理解瓦糕,現(xiàn)分析如下:

  1. 串行隊列,同步執(zhí)行-----串行隊列意味著順序執(zhí)行腋么,同步執(zhí)行意味著不開啟線程(在當(dāng)前線程執(zhí)行)
  1. 串行隊列,異步執(zhí)行-----串行隊列意味著任務(wù)順序執(zhí)行咕娄,異步執(zhí)行說明要開線程, (如果開多個線程的話,不能保證串行隊列順序執(zhí)行珊擂,所以只開一個線程)
  2. 并行隊列,異步執(zhí)行-----并行隊列意味著執(zhí)行順序不確定圣勒,異步執(zhí)行意味著會開啟線程,而并行隊列又允許不按順序執(zhí)行摧扇,所以系統(tǒng)為了提高性能會開啟多個線程圣贸,來隊列取任務(wù)(隊列中任務(wù)取出仍然是順序取出的,只是線程執(zhí)行無序)扛稽。
  3. 并行隊列,同步執(zhí)行-----同步執(zhí)行意味著不開線程,則肯定是順序執(zhí)行
  4. 死鎖-----程序執(zhí)行不出來(死鎖) 吁峻;

四、死鎖舉例

  • 主隊列死鎖:

這種死鎖最常見在张,問題也最嚴(yán)重用含,會造成主線程卡住。原因:主隊列帮匾,如果主線程正在執(zhí)行代碼耕餐,就不調(diào)度任務(wù);同步執(zhí)行:一直執(zhí)行第一個任務(wù)直到結(jié)束辟狈。兩者互相等待造成死鎖肠缔,示例如下:

- (void)mainThreadDeadLockTest {
    NSLog(@"begin");
    dispatch_sync(dispatch_get_main_queue(), ^{
        // 發(fā)生死鎖下面的代碼不會執(zhí)行
        NSLog(@"middle");
    });
    // 發(fā)生死鎖下面的代碼不會執(zhí)行,當(dāng)然函數(shù)也不會返回哼转,后果也最為嚴(yán)重
    NSLog(@"end");
}
  • 在其它線程死鎖明未,這種不會影響主線程:

原因:serialQueue為串行隊列,當(dāng)代碼執(zhí)行到block1時正常壹蔓,執(zhí)行到dispatch_sync時趟妥,dispatch_sync等待block2執(zhí)行完畢才會返回,而serialQueue是串行隊列佣蓉,它正在執(zhí)行block1披摄,只有等block1執(zhí)行完畢后才會去執(zhí)行block2亲雪,相互等待造成死鎖

- (void)deadLockTest {
    // 其它線程的死鎖
    dispatch_queue_t serialQueue = dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
        // 串行隊列block1
        NSLog(@"begin");
        dispatch_sync(serialQueue, ^{
            // 串行隊列block2 發(fā)生死鎖,下面的代碼不會執(zhí)行
            NSLog(@"middle");
        });
        // 不會打印
        NSLog(@"end");
    });
    // 函數(shù)會返回疚膊,不影響主線程
    NSLog(@"return");
}

五义辕、常用舉例

  • 線程間通訊

比如,為了提高用戶體驗寓盗,我們一般在其他線程(非主線程)下載圖片或其它網(wǎng)絡(luò)資源灌砖,下載完成后我們要更新UI,而UI更新必須在主線程執(zhí)行傀蚌,所以我們經(jīng)常會使用:

// 同步執(zhí)行基显,會阻塞指導(dǎo)下面block中的代碼執(zhí)行完畢
dispatch_sync(dispatch_get_main_queue(), ^{
    // 主線程,UI更新
});
// 異步執(zhí)行
dispatch_async(dispatch_get_main_queue(), ^{
    // 主線程善炫,UI更新
});
  • 信號量的使用

也屬于線程間通訊撩幽,下面的舉例是經(jīng)常用到的場景。在網(wǎng)絡(luò)訪問中箩艺,NSURLSession類都是異步的(找了很久沒有找到同步的方法)窜醉,而有時我們希望能夠像NSURLConnection一樣可以同步訪問,即在網(wǎng)絡(luò)block調(diào)用完成之后做一些操作舅桩。那我們可以使用dispatch的信號量來解決:

/// 用于線程間通訊酱虎,下面是等待一個網(wǎng)絡(luò)完成
- (void)dispatchSemaphore {
    NSString *urlString = [@"https://www.baidu.com" stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
    // 設(shè)置緩存策略為每次都從網(wǎng)絡(luò)加載 超時時間30秒
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30];
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        // 處理完成之后雨膨,發(fā)送信號量
        NSLog(@"正在處理...");
        dispatch_semaphore_signal(semaphore);
    }] resume];
    // 等待網(wǎng)絡(luò)處理完成
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"處理完成擂涛!");
}

在上面的舉例中dispatch_semaphore_signal的調(diào)用必須是在另一個線程調(diào)用,因為當(dāng)前線程已經(jīng)dispatch_semaphore_wait阻塞聊记。另外撒妈,dispatch_semaphore_wait最好不要在主線程調(diào)用

  • 全局隊列,實現(xiàn)并發(fā):
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    // 要執(zhí)行的代碼
});

六排监、Dispatch Group調(diào)度組

使用調(diào)度組狰右,可以輕松實現(xiàn)在一些任務(wù)完成后,做一些操作舆床。比如具有順序性要求的生產(chǎn)者消費者等等棋蚌。

  • 示例1:任務(wù)1完成之后執(zhí)行任務(wù)2。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self groupTest];
}
- (void)groupTest {
    // 創(chuàng)建一個組
    dispatch_group_t group = dispatch_group_create();
    NSLog(@"開始執(zhí)行");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            // 任務(wù)1
            // 等待1s一段時間在執(zhí)行
            [NSThread sleepForTimeInterval:1];
            NSLog(@"task1 running in %@",[NSThread currentThread]);
        });
        dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
            // 任務(wù)2
            NSLog(@"task2 running in %@",[NSThread currentThread]);
        });
    });
}

點擊屏幕后挨队,打印如下谷暮,可以看到任務(wù)1雖然等待了1s,任務(wù)2也不執(zhí)行盛垦,只有任務(wù)1執(zhí)行完畢才執(zhí)行任務(wù)2.

2015-08-28 18:16:05.317 GCDTest[1468:229374] 開始執(zhí)行
2015-08-28 18:16:06.323 GCDTest[1468:229457] task1 running in <NSThread: 0x7f8962f16900>{number = 2, name = (null)}
2015-08-28 18:16:06.323 GCDTest[1468:229456] task2 running in <NSThread: 0x7f8962c92750>{number = 3, name = (null)}
  • 示例2:其實示例1并不常用湿弦,真正用到的是監(jiān)控多個任務(wù)完成之后,回到主線程更新UI腾夯,或者做其它事情颊埃。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self groupTest];
}
- (void)groupTest {
    // 創(chuàng)建一個組
    dispatch_group_t group = dispatch_group_create();
    NSLog(@"開始執(zhí)行");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            // 關(guān)聯(lián)任務(wù)1
            NSLog(@"task1 running in %@",[NSThread currentThread]);
        });
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            // 關(guān)聯(lián)任務(wù)2
            NSLog(@"task2 running in %@",[NSThread currentThread]);
        });
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            // 關(guān)聯(lián)任務(wù)3
            NSLog(@"task3 running in %@",[NSThread currentThread]);
        });
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            // 關(guān)聯(lián)任務(wù)4
            // 等待1秒
            [NSThread sleepForTimeInterval:1];
            NSLog(@"task4 running in %@",[NSThread currentThread]);
        });
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            // 回到主線程執(zhí)行
            NSLog(@"mainTask running in %@",[NSThread currentThread]);
        });
    });
}

點擊屏幕后蔬充,打印如下,可以看到無論其它任務(wù)然后和執(zhí)行班利,mainTask等待它們執(zhí)行后才執(zhí)行饥漫。

2015-08-28 18:24:14.312 GCDTest[1554:236273] 開始執(zhí)行
2015-08-28 18:24:14.312 GCDTest[1554:236352] task3 running in <NSThread: 0x7fa8f1f0c9c0>{number = 4, name = (null)}
2015-08-28 18:24:14.312 GCDTest[1554:236354] task1 running in <NSThread: 0x7fa8f1d10750>{number = 2, name = (null)}
2015-08-28 18:24:14.312 GCDTest[1554:236351] task2 running in <NSThread: 0x7fa8f1c291a0>{number = 3, name = (null)}
2015-08-28 18:24:15.313 GCDTest[1554:236353] task4 running in <NSThread: 0x7fa8f1d0e7f0>{number = 5, name = (null)}
2015-08-28 18:24:15.313 GCDTest[1554:236273] mainTask running in <NSThread: 0x7fa8f1c13df0>{number = 1, name = main}

關(guān)于Dispatch對象內(nèi)存管理問題

根據(jù)上面的代碼,可以看出有關(guān)dispatch的對象并不是OC對象肥败,那么趾浅,用不用像對待Core Foundation框架的對象一樣,使用retain/release來管理呢馒稍?答案是不用的皿哨!

如果是ARC環(huán)境,我們無需管理纽谒,會像對待OC對象一樣自動內(nèi)存管理证膨。
如果是MRC環(huán)境,不是使用retain/release鼓黔,而是使用dispatch_retain/dispatch_release來管理央勒。

參考:http://www.cnblogs.com/mddblog/p/4767559.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市澳化,隨后出現(xiàn)的幾起案子崔步,更是在濱河造成了極大的恐慌,老刑警劉巖缎谷,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件井濒,死亡現(xiàn)場離奇詭異,居然都是意外死亡列林,警方通過查閱死者的電腦和手機瑞你,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來希痴,“玉大人者甲,你說我怎么就攤上這事∑龃矗” “怎么了虏缸?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嫩实。 經(jīng)常有香客問我刽辙,道長,這世上最難降的妖魔是什么舶赔? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任扫倡,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘撵溃。我一直安慰自己疚鲤,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布缘挑。 她就那樣靜靜地躺著集歇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪语淘。 梳的紋絲不亂的頭發(fā)上诲宇,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音惶翻,去河邊找鬼姑蓝。 笑死,一個胖子當(dāng)著我的面吹牛吕粗,可吹牛的內(nèi)容都是我干的纺荧。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼颅筋,長吁一口氣:“原來是場噩夢啊……” “哼宙暇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起议泵,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤占贫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后先口,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體型奥,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年池充,在試婚紗的時候發(fā)現(xiàn)自己被綠了桩引。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缎讼。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡收夸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出血崭,到底是詐尸還是另有隱情卧惜,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布夹纫,位于F島的核電站咽瓷,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏舰讹。R本人自食惡果不足惜茅姜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望月匣。 院中可真熱鬧钻洒,春花似錦奋姿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至头遭,卻和暖如春寓免,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背计维。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工袜香, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鲫惶。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓困鸥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親剑按。 傳聞我的和親對象是個殘疾皇子疾就,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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