【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_signal、dispatch_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多線程》