GCD全程Grand Central Dispath,是蘋果提供的一套多核并行運(yùn)算的解決方案什湘,GCD使用純C語言的API,提供了非常強(qiáng)大的API,它會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核涣澡、四核),自動(dòng)管理線程的生命周期(創(chuàng)建線程丧诺、調(diào)度線程入桂、銷毀線程),我們只需要告訴GCD想要執(zhí)行什么任務(wù)锅必,不需要編寫任何線程管理代碼事格。
函數(shù)、隊(duì)列與任務(wù)
GCD常見的用法
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"GCD");
});
可以看到GCD的使用分為3個(gè)部分搞隐,函數(shù)dispatch_sync
驹愚、隊(duì)列dispatch_get_global_queue(0, 0)
,以及任務(wù)block劣纲。
函數(shù)
在GCD中可以將執(zhí)行函數(shù)分為同步和異步兩種
- 同步函數(shù):
dispatch_sync
逢捺,必須等待當(dāng)前任務(wù)block執(zhí)行完畢后才能執(zhí)行下一語句,同步函數(shù)不會(huì)開啟線程癞季,會(huì)在當(dāng)前線程中執(zhí)行任務(wù) - 異步函數(shù):
dispatch_async
劫瞳,不用等待當(dāng)前任務(wù)block執(zhí)行完畢就可以執(zhí)行下一語句,可以開啟線程執(zhí)行線程
隊(duì)列
GCD隊(duì)列是任務(wù)的等待隊(duì)列绷柒,遵循先進(jìn)先出(FIFO)原則志于,沒調(diào)度一個(gè)任務(wù)就從隊(duì)列中釋放一個(gè)任務(wù),隊(duì)列有兩種:串行隊(duì)列和并發(fā)隊(duì)列废睦,兩者的主要區(qū)別為執(zhí)行順序不同伺绽,開啟線程數(shù)不同
串行隊(duì)列:每次只能有一個(gè)任務(wù)執(zhí)行,任務(wù)一個(gè)接著一個(gè)執(zhí)行嗜湃,一個(gè)任務(wù)執(zhí)行完畢后奈应,在執(zhí)行下一個(gè)任務(wù)
dispatch_queue_t queue = dispatch_queue_create("net.gcd.queue", DISPATCH_QUEUE_SERIAL);
并發(fā)隊(duì)列:可以讓多個(gè)任務(wù)并發(fā)執(zhí)行,可以開啟多想線程
dispatch_queue_t queue = dispatch_queue_create("net.gcd.queue", DISPATCH_QUEUE_CONCURRENT);
并發(fā)隊(duì)列的并發(fā)功能只有在異步函數(shù)下才有效
#define DISPATCH_QUEUE_SERIAL NULL
,所以dispatch_queue_create("", NULL)
得到的是同步隊(duì)列
主隊(duì)列
GCD提供了一種特殊的串行隊(duì)列--主隊(duì)列购披,所有放在隊(duì)列中的任務(wù)都會(huì)放到主隊(duì)列中執(zhí)行
全局并發(fā)隊(duì)列
GCD默認(rèn)提供了全局并發(fā)隊(duì)列杖挣,他需要提供兩個(gè)參數(shù),第一個(gè)是隊(duì)列優(yōu)先級(jí)刚陡,通常用DISPATCH_QUEUE_PRIORITY_DEFAULT
,在iOS9之后惩妇,已經(jīng)被服務(wù)質(zhì)量代替株汉,如QOS_CLASS_DEFAULT
,第二個(gè)參數(shù)是蘋果的保留字段屿附,一般寫0.
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
在使用多線程開發(fā)時(shí)郎逃,如果對(duì)隊(duì)列沒有特殊需求,可以直接使用全局并發(fā)隊(duì)列
函數(shù)與隊(duì)列
根據(jù)函數(shù)與隊(duì)列的兩兩組合挺份,會(huì)出現(xiàn)出現(xiàn)以下四種結(jié)果
同步函數(shù)并發(fā)隊(duì)列:
- 不會(huì)開啟線程褒翰,在當(dāng)前線程執(zhí)行任務(wù)
- 任務(wù)串行執(zhí)行,任務(wù)一個(gè)接著一個(gè)
- 可能會(huì)產(chǎn)生堵塞
同步函數(shù)并發(fā)隊(duì)列:
- 不會(huì)開啟線程匀泊,在當(dāng)前線程執(zhí)行任務(wù)
- 任務(wù)一個(gè)接著一個(gè)
異步函數(shù)串行隊(duì)列:
- 開啟一條新線程
- 任務(wù)一個(gè)接著一個(gè)
異步函數(shù)并發(fā)隊(duì)列:
- 開啟多個(gè)線程
- 異步執(zhí)行任務(wù)优训,沒有順序,與CPU調(diào)度有關(guān)
GCD的其他方法
除了開啟線程的能力各聘,GCD還提供了許多api揣非,我們可以利用這些api實(shí)現(xiàn)單例、多讀單寫躲因、計(jì)時(shí)器等功能
柵欄函數(shù) dispatch_barrier_async
如果我們需要異步執(zhí)行兩組操作早敬,而且第一組操作執(zhí)行完成之后才能執(zhí)行第二組操作,我們就需要一個(gè)柵欄一樣的東西將這兩組操作分割開來大脉,而GCD的dispatch_barrier_async
便可以實(shí)現(xiàn)著這一功能搞监。
dispatch_barrier_async
會(huì)等待前邊追加到并發(fā)隊(duì)列的任務(wù)全部執(zhí)行完畢后,再將指定的任務(wù)追加到該異步隊(duì)列中镰矿,然后在dispatch_barrier_async
追加的任務(wù)執(zhí)行完畢之后琐驴,異步隊(duì)列才恢復(fù)為正常執(zhí)行矗积。
例如:
dispatch_queue_t queue = dispatch_queue_create("barrier_test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"1");
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"2");
}
});
dispatch_barrier_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"barrier");
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"3");
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"4");
}
});
執(zhí)行結(jié)果為
2020-11-25 16:05:29.664252+0800 GCDTest[91450:4599536] 1
2020-11-25 16:05:29.664294+0800 GCDTest[91450:4599535] 2
2020-11-25 16:05:31.668346+0800 GCDTest[91450:4599535] 2
2020-11-25 16:05:31.668442+0800 GCDTest[91450:4599536] 1
2020-11-25 16:05:31.668615+0800 GCDTest[91450:4599536] barrier
2020-11-25 16:05:31.668735+0800 GCDTest[91450:4599536] barrier
2020-11-25 16:05:33.670656+0800 GCDTest[91450:4599536] 3
2020-11-25 16:05:33.670617+0800 GCDTest[91450:4599535] 4
2020-11-25 16:05:35.675116+0800 GCDTest[91450:4599535] 4
2020-11-25 16:05:35.675144+0800 GCDTest[91450:4599536] 3
利用這一特性梁只,我們可以使用柵欄函數(shù)實(shí)現(xiàn)多讀單寫。
- (void)setName:(NSString *)name {
dispatch_barrier_async(self.queue, ^{
self->_name = [name copy];
});
}
- (NSString *)name {
__block NSString *tempName;
dispatch_sync(self.queue, ^{
tempName = self->_name;
});
return tempName;
}
- (dispatch_queue_t)queue {
if (!_queue) {
_queue = dispatch_queue_create("barrier_test", DISPATCH_QUEUE_CONCURRENT);
}
return _queue;
}
隊(duì)列組 dispatch_group
有時(shí)候我們會(huì)有這樣的需求锄贷,分別執(zhí)行兩個(gè)異步耗時(shí)任務(wù)苍姜,然后當(dāng)兩個(gè)耗時(shí)任務(wù)都執(zhí)行完畢后再回到主線程執(zhí)行任務(wù)牢酵,這時(shí)候可以用到GCD的隊(duì)列組。
GCD的隊(duì)列組有幾個(gè)關(guān)鍵的api
-
dispatch_group_create
創(chuàng)建隊(duì)列組 -
dispatch_group_enter
加入隊(duì)列組 -
dispatch_group_leave
離開隊(duì)列組 -
dispatch_group_async
將任務(wù)加入隊(duì)列組 -
dispatch_group_notify
回到指定隊(duì)列執(zhí)行任務(wù) -
dispatch_group_wait
同上衙猪,但會(huì)阻塞當(dāng)前線程
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"1");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"2");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"3");
});
執(zhí)行結(jié)果:
2020-11-26 17:33:44.591118+0800 GCDTest[6468:5517558] 2
2020-11-26 17:33:44.591118+0800 GCDTest[6468:5517552] 1
2020-11-26 17:33:44.591338+0800 GCDTest[6468:5517552] 3
GCD一次性代碼 dispatch_once
我們?cè)趧?chuàng)建單例茁帽、或者有整個(gè)運(yùn)行過程中只執(zhí)行一次的代碼時(shí),我們可以使用GCD的dispatch_once
實(shí)現(xiàn)屈嗤,即使在多線程的環(huán)境,dispatch_once
也可以保證線程安全吊输。
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行一次的代碼
});
}
GCD計(jì)時(shí)器 dispatch_source
dispatch source 是一種處理事件的數(shù)據(jù)類型饶号,這些被處理的事件為操作系統(tǒng)中的底層級(jí)別,而計(jì)時(shí)器類型是其支持的其中一種類型季蚂。
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0.0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_timer, ^{
NSLog(@"source");
});
dispatch_resume(_timer);
-
dispatch_source_create
:創(chuàng)建source茫船,計(jì)時(shí)器類型使用DISPATCH_SOURCE_TYPE_TIMER
琅束,而且可以指定隊(duì)列 -
dispatch_source_set_timer
:設(shè)置定時(shí)器的相關(guān)參數(shù),第一個(gè)參數(shù)為定時(shí)器算谈,第二個(gè)為開始時(shí)間涩禀,第三個(gè)為回調(diào)時(shí)間間隔,第四個(gè)允許誤差范圍 -
dispatch_source_set_event_handler
:設(shè)置事件處理句柄然眼,一個(gè)句柄可以是一個(gè)block或者是一個(gè)函數(shù)艾船,dispatch source會(huì)把句柄投放到隊(duì)列中執(zhí)行。 -
dispatch_resume
:timer默認(rèn)是掛起的高每,需要手動(dòng)開啟
和NSTimer
相比屿岂,GCD的timer不需要依賴runloop,不會(huì)因?yàn)閞unloop的繁忙而導(dǎo)致及時(shí)不準(zhǔn)鲸匿。
GCD快速迭代方法 dispatch_apply
和for循環(huán)類似爷怀,dispatch_apply
可以快速循環(huán)遍歷。而相比于普通的for循環(huán)带欢,dispatch_apply
按照指定次數(shù)將任務(wù)追加到指定的隊(duì)列中运授,并等待全部隊(duì)列執(zhí)行結(jié)束。
如果在串行隊(duì)列中使用dispatch_apply
乔煞,那么就和for循環(huán)一樣按照順序同步執(zhí)行吁朦,沒有快速迭代的意義,我們可以利用并發(fā)隊(duì)列隊(duì)形進(jìn)行異步執(zhí)行瘤缩,讓dispatch_apply
的任務(wù)多個(gè)線程中不是異步執(zhí)行喇完,并且無論在串行隊(duì)列還是異步隊(duì)列,dispatch_apply
都會(huì)等到全部任務(wù)執(zhí)行完成剥啤,有點(diǎn)類似dispatch_group_wait
方法锦溪。
可以用下面的例子清楚了解:
NSLog(@"start");
dispatch_apply(10, queue, ^(size_t i) {
NSLog(@"%zd-----%@", i, [NSThread currentThread]);
});
NSLog(@"end");
執(zhí)行結(jié)果為:
2020-11-27 15:35:23.341807+0800 GCDTest[44897:5974416] start
2020-11-27 15:35:23.341930+0800 GCDTest[44897:5974416] 0-----<NSThread: 0x600001700040>{number = 1, name = main}
2020-11-27 15:35:23.341998+0800 GCDTest[44897:5974416] 2-----<NSThread: 0x600001700040>{number = 1, name = main}
2020-11-27 15:35:23.342015+0800 GCDTest[44897:5974466] 1-----<NSThread: 0x60000173a500>{number = 2, name = (null)}
2020-11-27 15:35:23.342048+0800 GCDTest[44897:5974416] 3-----<NSThread: 0x600001700040>{number = 1, name = main}
2020-11-27 15:35:23.342096+0800 GCDTest[44897:5974416] 5-----<NSThread: 0x600001700040>{number = 1, name = main}
2020-11-27 15:35:23.342093+0800 GCDTest[44897:5974466] 4-----<NSThread: 0x60000173a500>{number = 2, name = (null)}
2020-11-27 15:35:23.342180+0800 GCDTest[44897:5974466] 7-----<NSThread: 0x60000173a500>{number = 2, name = (null)}
2020-11-27 15:35:23.342188+0800 GCDTest[44897:5974416] 6-----<NSThread: 0x600001700040>{number = 1, name = main}
2020-11-27 15:35:23.342316+0800 GCDTest[44897:5974466] 8-----<NSThread: 0x60000173a500>{number = 2, name = (null)}
2020-11-27 15:35:23.342346+0800 GCDTest[44897:5974464] 9-----<NSThread: 0x60000174e880>{number = 3, name = (null)}
2020-11-27 15:35:23.342849+0800 GCDTest[44897:5974416] end
GCD信號(hào)量 dispatch_semaphore
GCD中的信號(hào)量指的是dispatch_semaphore
,是持有計(jì)數(shù)的信號(hào),類似高速路收費(fèi)站的欄桿府怯,可以通過時(shí)刻诊,打開欄桿,不可以通過時(shí)牺丙,關(guān)閉欄桿则涯。在dispatch_semaphore
中,使用計(jì)數(shù)來完成這個(gè)功能冲簿,計(jì)數(shù)為0時(shí)等待粟判,不可通過,計(jì)數(shù)為1或大于1時(shí)峦剔,計(jì)數(shù)減1且不等待档礁,可通過。
dispatch_semaphore
提供了三個(gè)函數(shù)吝沫。
-
dispatch_semaphore_create
:創(chuàng)建一個(gè)Semaphore并初始化信號(hào)的總量 -
dispatch_semaphore_signal
:發(fā)送一個(gè)信號(hào)呻澜,讓信號(hào)總量加1 -
dispatch_semaphore_wait
:可以使總信號(hào)量減1递礼,當(dāng)信號(hào)量為0時(shí)就會(huì)一直等待(阻塞所有線程),否則可以正常執(zhí)行
基于dispatch_semaphore
的這一性質(zhì)羹幸,它常常被用來作為鎖來實(shí)現(xiàn)線程同步脊髓,如下
NSLog(@"current thread: %@", [NSThread currentThread]);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int num = 0;
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"current thread: %@", [NSThread currentThread]);
num = 100;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"num = %d", num);
如果沒有加入dispatch_semaphore
,那么NSLog(@"num = %d", num);
便不會(huì)等待num = 100;
執(zhí)行完成栅受,而是直接打印當(dāng)前數(shù)值0将硝,而加入dispatch_semaphore_t
后,開始semaphore
為0窘疮,執(zhí)行到
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
時(shí)袋哼,因?yàn)?code>semaphore為0,所以下面的代碼不會(huì)執(zhí)行闸衫,而子線程中的任務(wù)執(zhí)行num = 100;
完成后涛贯,會(huì)調(diào)用一遍dispatch_semaphore_signal(semaphore);
將semaphore
加一變?yōu)?,此時(shí)會(huì)通知到dispatch_semaphore
蔚出,從而繼續(xù)往下執(zhí)行NSLog(@"num = %d", num);
弟翘。