GCD與多線程編程

【Xcode-Men】Hi,我們團隊的井小二童鞋給我們取了個隊名:Xcode-Men,簡稱X-Men,是不是屌屌的。我們開這個博客,是想記錄下記錄下每位小伙伴的成長,以及我們團隊的成長。我們的團隊很年輕,準90后和90后已經成了我們的主力。希望他們在不久的將來,能脫穎而出;也希望他們離開團隊時,能非常自豪地說自己曾經是X-Men的一員,這是一種情懷,情懷【噫,好像不能用表情】。

這是我們團隊的第一篇技術總結,由王瑞華童鞋撰寫??偨Y了自己在開發過程GCD的一些心得。歡迎大家吐槽。

最近在開發股神的項目中用到很多GCD,所以總結了一下GCD的一些使用。

1.?什么是GCD


官方文檔是這么說的:

>Grand Central Dispatch (GCD) comprises language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in iOS and OS X.

大致意思是說,GCD是異步執行任務的技術,提供語言特征、運行時庫、系統全面的改進的多核硬件上的支持。開發者只需要定義想執行的任務并加入到隊列中就可以了。

也就是說,GCD用我們難以置信的非常簡潔的記述方法,實現了極為復雜繁瑣的多線程編程,可以說是一項劃時代的技術。

同時它具有很多優點:

- 直觀而簡單的編程接口

- 提供自動和整體的線程池管理

- 提供匯編級調優的速度

- 更加高效的使用內存

- 不會trap內核

- 異步分派任務到dispatch queue,不對導致queue死鎖

- 伸縮性強

- serial dispatch queue比鎖和其他同步原語更加高效

2 GCD APIs


2.1串行隊列Serial Diapatch Queue

創建串行隊列,對于第二個參數可為DISPATCH_QUEUE_SERIAL或者NULL,隊列中操作會按順序執行。

dispatch_queue_t myQueue =?dispatch_queue_create("com.my.wangruih", DISPATCH_QUEUE_SERIAL);

dispatch_async(myQueue, ^{

? ? NSLog(@"1");

});

dispatch_async(myQueue, ^{

? ? NSLog(@"2");

});

dispatch_async(myQueue, ^{

? ? NSLog(@"3");

});

以上語句的輸出結果為:

2016-06-27 15:11:40.612 TestGCD[34781:4486699] 1

2016-06-27 15:11:40.613 TestGCD[34781:4486699] 2

2016-06-27 15:11:40.613 TestGCD[34781:4486699] 3

三者相互依賴,串行執行。

2.2并行隊列Concurrent Diapatch Queue

往隊列中追加的操作,沒有相互依賴關系,執行會放到不同的線程中,執行的先后順序未知。

dispatch_queue_t myQueue

= dispatch_queue_create("com.my.wangruih", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(myQueue, ^{

? ? NSLog(@"1");

});

dispatch_async(myQueue, ^{

? ? NSLog(@"2");

});

dispatch_async(myQueue, ^{

? ? NSLog(@"3");

});

運行后發現日志為:

2016-06-27 15:18:23.895 TestGCD[34834:4493908] 2

2016-06-27 15:18:23.895 TestGCD[34834:4493899] 1

2016-06-27 15:18:23.895 TestGCD[34834:4493934] 3

當然這只是其中的一種情況,按照排列組合計算,可以產生6種不同的順序。

如前所訴,concurrent dispatch queue并行執行多個處理,而serial只能執行1個追加處理。雖然一個serial queue只能執行一個,但是可以創建多個,代價便是產生多個線程,過多的線程會消耗大量內存,頻繁的上下文切換會大幅降低性能。

2.3 Main Dispatch Queue/Global Dispatch Queue

在我們不打算自己生成dispatch queue的情況下,系統會為我們準備兩個,那就是Main Dispatch Queue(就是serial queue),Global Dispatch Queue(就是concurrent queue)。

- Main顧名思義就是主線程,所有的操作都會追加到主線程去執行,大多都是用戶界面的更新。

- Global是說有程序使用的concurrent queue,同時它具有四個優先級分別為High,Default,Low,Background。通過XNU內核根據優先級來調度線程執行。

3?說完了隊列再來說說,操作隊列的一些方法


3.1 dispath_set_target_queue

變更生成的dispatch queue的執行優先級使用dispatch_set_target_queue函數,在實際的工作里我倒是沒用到過該方法。

dispatch_queue_t mySerialQueue = dispatch_queue_create("com.my.wangruih", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t myGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

dispatch_set_target_queue(mySerialQueue, myGlobalQueue);

dispatch_async(mySerialQueue, ^{

? ? NSLog(@"hello");

});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

? ? NSLog(@"world");

});

執行結果如下,根據結果看出該方法修改了優先級。

2016-06-27 15:46:05.972 TestGCD[34901:4517280] world

2016-06-27 15:46:05.973 TestGCD[34901:4517287] hello

3.2 dispatch_after

這個在工作中倒是常常用到,我常用在tableview reloaddata場景中,因為有一些諸如點贊動畫之類的,加入該方法是的點贊動畫執行完畢后的0.5~1s再執行reloadData操作。

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

? ? NSLog(@"what happened");

});

// 輸出

2016-06-27 15:51:07.921 TestGCD[34918:4521806] hello

2016-06-27 15:51:11.216 TestGCD[34918:4521762] what happened

大致該語句執行是在3秒之后,也就是3秒之后追加block到main queue。這個時間并不是絕對的,block只是追加到了main runloop中,而main queue可能有大量其他處理,會使得時間變長。第一個參數是指定的dispatch_time_t類型。該值有dispatch_time或dispatch_walltime。前者通常用于相對時間,后者為絕對時間。如希望在2016/11/11 -0:0執行。

dispatch_time_t getDispatchTimeByData(NSDate *date) {

? ? NSTimeInterval interval;

? ? double second, subsecond;

? ? struct timespec time;

? ? dispatch_time_t milestone;

? ? interval = [date timeIntervalSince1970];

? ? subsecond = modf(interval, &second);

? ? time.tv_sec = second;

? ? time.tv_nsec = subsecond * NSEC_PER_SEC;

? ? milestone = dispatch_walltime(&time, 0);

? ? return milestone;

}

NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:5];

dispatch_after(getDispatchTimeByData(date), dispatch_get_main_queue(), ^{

? ? NSLog(@"happen sth");

});

// 輸出

2016-06-27 16:04:06.949 TestGCD[34946:4531114] what happened

2016-06-27 16:04:09.439 TestGCD[34946:4531114] happen sth

3.3 dispatch_group

如果有3個操作(A,B,C)需要在并行執行完A,B之后再執行C操作,可以有多個實現方式,當然可以通過添加依賴關系addDependency來實現。通過dispatch_group的方式依然可以實現。

無論向什么樣的dispatch queue中追加處理,使用dispatch group都能監視到所有處理的結束,就可以將結束的處理追加到dispatch queue中,這是使用dispatch group的原因。

dispatch_group_t group = dispatch_group_create();

dispatch_queue_t queue = dispatch_queue_create("com.my.wangruih", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_async(group, queue, ^{

? ? NSLog(@"A");

});

dispatch_group_async(group, queue, ^{

? ? NSLog(@"B");

});

dispatch_group_notify(group, queue, ^{

? ? NSLog(@"C");

});

// 輸出

2016-06-28 09:07:18.261 TestGCD[35461:4595668] B

2016-06-28 09:07:18.261 TestGCD[35461:4595678] A

2016-06-28 09:07:18.262 TestGCD[35461:4595668] C

3.4 dispatch_barrier_async

在訪問數據庫或者文件時,有可能發生數據競爭。此方法的作用便是在并發隊列中,完成在它之前提交到隊列中的任務后打斷,單獨執行其block。起到了一個線程鎖的作用。同樣適用于上節中的,A,B,C的執行順序問題。

dispatch_queue_t queue = dispatch_queue_create("com.my.wangruih", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{

? ? NSLog(@"A");

});

dispatch_async(queue, ^{

? ? NSLog(@"B");

});

dispatch_barrier_async(queue, ^{

? ? NSLog(@"C");

});

dispatch_async(queue, ^{

? ? NSLog(@"D");

});

// 輸出

2016-06-28 09:21:43.864 TestGCD[35515:4607006] A

2016-06-28 09:21:43.864 TestGCD[35515:4607015] B

2016-06-28 09:21:43.865 TestGCD[35515:4607015] C

2016-06-28 09:21:43.865 TestGCD[35515:4607015] D

3.5 dispatch_sync

它以為著事件是同步發生的,也就是指定的block同步追加到指定的dispatch queue中。在追加block結束之前,dispatch_sync函數會一直等待。這里有一個經常提及的問題來考察對同步的理解。

題目如下,輸出結果是什么,為什么會這樣。

NSLog(@"1");

dispatch_sync(dispatch_get_main_queue(), ^{

? ? NSLog(@"2");

});

NSLog(@"3");

輸出結果是:

2016-06-28 09:28:10.713 TestGCD[35528:4611901] 1

原因:由于main queue是串行queue,采用FIFO執行任務,也就是block操作加在了隊列之后,dispathc_sync堵塞了主線程等待block語句完成后執行main thread,但block語句由于線程阻塞永不會執行,所以導致一直等待死鎖。

3.6 dispatch_asyc

與sync不同,它是非同步的追加到指定的dispatch queue中。dispatch_async函數不做任何等待。

這里也有一個對于該函數理解的題目,如下,推斷他的執行順序

NSLog(@"1");

dispatch_async(dispatch_get_main_queue(), ^{

? ? NSLog(@"2");

});

for (int i = 0; i < 10; i++) {

NSLog(@"3");

}

打印log日志為:

2016-06-28 09:39:16.048 TestGCD[35575:4619720] 1

2016-06-28 09:39:16.049 TestGCD[35575:4619720] 3

2016-06-28 09:39:16.049 TestGCD[35575:4619720] 3

2016-06-28 09:39:16.050 TestGCD[35575:4619720] 3

2016-06-28 09:39:16.050 TestGCD[35575:4619720] 3

2016-06-28 09:39:16.050 TestGCD[35575:4619720] 3

2016-06-28 09:39:16.059 TestGCD[35575:4619720] 2

也就是說**NSLog(@"2");**永遠會在for循環之后執行。原因:main queue為串行隊列,遵循FIFO原則,同時為異步執行,異步block添加到隊列中的**“不等待”**立刻執行for循環,在下一次runloop時才會執行block語句塊的內容。

3.7 dispatch_semaphore_t

dispatch semaphore是持有計數的信號,該計數是多線程中的計數類型信號。所謂信號,類似于過北京西站地鐵口安檢時常用的手牌??梢酝ㄟ^時舉起手牌,不可通過時放下手牌。而在dispatch semaphore中,使用計數來實現該功能。計數為0時等待,計數為1或者大于1時,減去1而不等待。

主要涉及三個函數?dipatch_semaphore_create()、dispatch_semphore_signaldispatch_semaphore_wait.

對于以上三個函數通常都用停車位來解釋,dipatch_semaphore_create()說明了初始車位數,沒調用一次dispatch_semphore_signal剩余車位數就增加一個,每調用dispatch_semaphore_wait剩余車位數減少一個,等車位數為0時,再來車(即調用dispatch_semaphore_wait)就只能等車位。

該函數同樣能解決上節中A,B,C的執行依賴問題。

dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(0);

dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

? ? NSLog(@"A");

dispatch_semaphore_signal(semaphore1);

});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

? ? NSLog(@"B");

? ? dispatch_semaphore_signal(semaphore2);

});

dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);

dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);

NSLog(@"C");

// 輸出

2016-06-28 10:01:33.658 TestGCD[35622:4632780] B

2016-06-28 10:01:33.658 TestGCD[35622:4632775] A

2016-06-28 10:01:33.660 TestGCD[35622:4632744] C

3.8 dispatch_apply

dispatch_apply函數是dispatch_sync函數和dispatch group的關聯API。該函數按指定的次數將指定的block追加到指定的dispatch queue中,并等待全部處理執行完成。

dispatch_apply(5, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {

? ? NSLog(@"%zu", index);

});

NSLog(@"done");

// 輸出

2016-06-28 10:10:25.110 TestGCD[35649:4638944] 3

2016-06-28 10:10:25.110 TestGCD[35649:4638889] 0

2016-06-28 10:10:25.110 TestGCD[35649:4638940] 2

2016-06-28 10:10:25.110 TestGCD[35649:4638934] 1

2016-06-28 10:10:25.111 TestGCD[35649:4638889] 4

2016-06-28 10:10:25.111 TestGCD[35649:4638889] done

第一個參數為重復的次數,第二個參數為追加對象的dispatch queue,第三個參數為追加的處理。由于*dispatch_apply*函數也與*dispatch_sync*函數相同,會等待處理執行結束,推薦在*dispatch_async*函數中非同步地執行*dispatch_apply*函數

參考


《Objective-C高級編程 iOS與OS X多線程》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容

  • 最近頗花了一番功夫把多線程GCD人的一些用法總結出來,一來幫自己鞏固一下知識、二來希望能幫到對這一塊還迷茫...
    人活一世閱讀 294評論 1 1
  • 一. 重點: 1.dispatch_queue_create(生成Dispatch Queue) 2.Main D...
    BestJoker閱讀 1,594評論 2 2
  • 本篇博客共分以下幾個模塊來介紹GCD的相關內容: 多線程相關概念 多線程編程技術的優缺點比較? GCD中的三種隊列...
    有夢想的老伯伯閱讀 1,030評論 0 4
  • iOS多線程之GCD 什么是GCD GCD(grand central dispatch) 是 libdispat...
    comst閱讀 1,218評論 0 0
  • 所有那些時刻將消失在時間裡,像雨中的淚水。
    荒木覺閱讀 115評論 0 3