GCD在iOS中多線程開發(fā)中使用頻繁,使用方便簡(jiǎn)單立哑,可以滿足我們大部分需求。其使用方法如下:
1、基本認(rèn)識(shí)
GCD以任務(wù)隊(duì)列為基礎(chǔ)的多線程管理的方案启妹,使用者不直接調(diào)用線程驱闷,而是調(diào)用隊(duì)列扶檐,往隊(duì)列中添加執(zhí)行任務(wù)迹缀,決定任務(wù)的執(zhí)行方式。
GCD中的隊(duì)列分為串行隊(duì)列种远、并行隊(duì)列涩澡,執(zhí)行方式分為同步執(zhí)行、異步執(zhí)行坠敷。
串行隊(duì)列:每次只能執(zhí)行一個(gè)任務(wù)妙同,等待任務(wù)執(zhí)行完畢才能執(zhí)行第下一個(gè)任務(wù)。
并行隊(duì)列:每次可以執(zhí)行多個(gè)任務(wù)膝迎,后面的任務(wù)不依賴前面的任務(wù)執(zhí)行情況粥帚。
同步執(zhí)行:執(zhí)行任務(wù)時(shí),需要當(dāng)前代碼執(zhí)行完畢才能執(zhí)行后面的代碼限次,會(huì)阻塞當(dāng)前線程
異步執(zhí)行:執(zhí)行任務(wù)時(shí)芒涡,當(dāng)前代碼不會(huì)影響后續(xù)任務(wù)外的代碼的執(zhí)行,不會(huì)阻塞當(dāng)前線程
創(chuàng)建方式如下:
self.serial_queue = dispatch_queue_create("串行隊(duì)列", DISPATCH_QUEUE_SERIAL);
self.concurrent_queue = dispatch_queue_create("并行隊(duì)列", DISPATCH_QUEUE_CONCURRENT);
其中隊(duì)列名稱可自行定義。
隊(duì)列和執(zhí)行方式有四種組合
a拖陆、串行隊(duì)列,同步執(zhí)行
任務(wù)按順序在當(dāng)前線程執(zhí)行懊亡,并且會(huì)阻塞當(dāng)前線程依啰,代碼如下:
- (void)sync_serial_queue {
for (int i = 0; i < 20; i++) {
dispatch_sync(self.serial_queue, ^{
NSLog(@"%d===%@",i,[NSThread currentThread]);
});
}
}
b、串行隊(duì)列店枣,異步執(zhí)行
任務(wù)按順序執(zhí)行速警,不一定在當(dāng)前線程,不會(huì)阻塞當(dāng)前線程鸯两,代碼如下:
- (void)async_serial_queue {
for (int i = 0; i < 20; i++) {
dispatch_async(self.serial_queue, ^{
NSLog(@"%d===%@",i,[NSThread currentThread]);
});
}
}
c闷旧、并行隊(duì)列,同步執(zhí)行
任務(wù)按順序執(zhí)行钧唐,不在當(dāng)前線程忙灼,會(huì)阻塞當(dāng)前線程,代碼如下:
- (void)sync_concurrent_queue {
for (int i = 0; i < 20; i++) {
dispatch_sync(self.concurrent_queue, ^{
NSLog(@"%d===%@",i,[NSThread currentThread]);
});
}
}
d钝侠、并行隊(duì)列该园,異步執(zhí)行
任務(wù)不按順序執(zhí)行,不在當(dāng)前線程帅韧,不會(huì)阻塞當(dāng)前線程里初,代碼如下:
- (void)async_concurrent_queue {
for (int i = 0; i < 20; i++) {
dispatch_async(self.concurrent_queue, ^{
NSLog(@"%d===%@",i,[NSThread currentThread]);
});
}
}
iOS的系統(tǒng)框架提供了兩個(gè)系統(tǒng)級(jí)隊(duì)列,分別為主隊(duì)列和全局隊(duì)列忽舟,特性如下:
主隊(duì)列:串行隊(duì)列双妨,任務(wù)只在主線程執(zhí)行
全局隊(duì)列:并行隊(duì)列,可設(shè)置優(yōu)先級(jí)
隊(duì)列獲取代碼如下:
//主隊(duì)列
dispatch_queue_t main_queue = dispatch_get_main_queue();
//全局隊(duì)列
dispatch_queue_t global_queue = dispatch_get_global_queue(0, 0);
在一般的需求下可以使用全局隊(duì)列叮阅,在代碼需要在主線程環(huán)境時(shí)使用主隊(duì)列刁品。
2、dispatch barrier的使用
當(dāng)我們存在以下情況帘饶,任務(wù)4依賴于任務(wù)1哑诊、任務(wù)2及刻、任務(wù)3的執(zhí)行,任務(wù)5缴饭、任務(wù)6、任務(wù)7依賴于任務(wù)4的執(zhí)行颗搂,其中任務(wù)1担猛、任務(wù)2、任務(wù)3可以分別獨(dú)立執(zhí)行傅联,任務(wù)5、任務(wù)6蒸走、任務(wù)7頁可以分別獨(dú)立同時(shí)執(zhí)行仇奶,如下圖:
則可以使用dispatch_barrier,函數(shù)有dispatch_barrier_sync和dispatch_barrier_async比驻,區(qū)別在于dispatch_barrier_sync會(huì)阻塞后續(xù)代碼的運(yùn)行别惦,dispatch_barrier_async不會(huì)阻塞后續(xù)代碼的運(yùn)行。兩個(gè)函數(shù)代碼如下:
/**
異步并行隊(duì)列掸掸,barrier同步,使barrier前面的任務(wù)全部執(zhí)行完畢点晴,才會(huì)執(zhí)行barrier后面添加到queue的任務(wù),barrier_sync會(huì)影響后阻塞后續(xù)代碼的執(zhí)行
*/
- (void)concurrent_queue_barier_sync {
NSLog(@"start");
for (int i = 0; i < 10; i++) {
dispatch_async(self.concurrent_queue, ^{
NSLog(@"%d===%@",i,[NSThread currentThread]);
});
if (i == 5) {
NSLog(@"barrier sync");
dispatch_barrier_sync(self.concurrent_queue, ^{
NSLog(@"dispatch_barrier_sync===%d===%@",i,[NSThread currentThread]);
});
}
}
NSLog(@"end");
}
/**
異步并行隊(duì)列悯周,barrier同步,使barrier前面的任務(wù)全部執(zhí)行完畢屠橄,才會(huì)執(zhí)行barrier后面添加到queue的任務(wù)闰挡,barrier_async不會(huì)影響后續(xù)代碼的執(zhí)行
*/
- (void)concurrent_queue_barier_async {
NSLog(@"start");
for (int i = 0; i < 10; i++) {
dispatch_async(self.concurrent_queue, ^{
NSLog(@"%d===%@",i,[NSThread currentThread]);
});
if (i == 5) {
NSLog(@"barrier async");
dispatch_barrier_async(self.concurrent_queue, ^{
NSLog(@"dispatch_barrier_async===%d===%@",i,[NSThread currentThread]);
});
}
}
NSLog(@"end");
}
3长酗、延遲執(zhí)行
當(dāng)我們需要延遲執(zhí)行某個(gè)任務(wù),一般情況下我們可以使用dispatch_after函數(shù)夺脾,代碼如下:
- (void)dispatch_after {
NSLog(@"%@",[NSDate date]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"after == %@",[NSDate date]);
});
}
swift版本
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2, execute: DispatchWorkItem(block: {
}))
4咧叭、單次執(zhí)行
當(dāng)我們需要?jiǎng)?chuàng)建單例或者只需要代碼在程序運(yùn)行過程中只執(zhí)行一次時(shí),我們可以使用dispatch_once函數(shù)吉挣,代碼如下:
- (void)dispatch_once {
for (int i = 0; i < 10; i++) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"%d",i);
});
}
}
5、多線程快速迭代
當(dāng)我們的數(shù)組需要在多線程情況下遍歷時(shí)可以使用dispatch_apply终吼,使用較少氯哮,代碼如下:
- (void)dispatch_apply {
dispatch_apply(10, self.concurrent_queue, ^(size_t index) {
NSLog(@"dispatch_apply==%zud===%@",index,[NSThread currentThread]);
});
}
6、多任務(wù)同步
當(dāng)我們需要實(shí)現(xiàn)下面的任務(wù)時(shí)蛙粘,除了第二項(xiàng)的dispatch_barrier,我們還可以使用dispatch_group出牧。任務(wù)4依賴于任務(wù)1歇盼、任務(wù)2、任務(wù)3的執(zhí)行伯复,如下圖:
主要操作步驟:1啸如、創(chuàng)建dispatch_group氮惯;2、把任務(wù)加入到group帘不,等待任務(wù)執(zhí)行完畢杨箭;3、任務(wù)執(zhí)行完畢捣郊,調(diào)用dispatch_group_notify里面的任務(wù)擒悬。
第2步把任務(wù)加入到group中的操作有兩種方法,一種是直接使用dispatch_group_async侈净,另外一種是使用dispatch_group_enter、dispatch_group_leave兩個(gè)函數(shù)元扔,dispatch_group_enter表示進(jìn)入group旋膳,dispatch_group_leave表示一個(gè)任務(wù)執(zhí)行完畢,離開group擅羞。
我們也可以使用同步函數(shù)dispatch_group_wait來等待任務(wù)1义图、任務(wù)2、任務(wù)3執(zhí)行完畢娃承,然后執(zhí)行dispatch_group_wait后面的代碼怕篷。
代碼如下:
- (void)dispatch_group_1 {
dispatch_group_t group_t = dispatch_group_create();
for (int i = 0; i < 10; i++) {
dispatch_group_async(group_t, self.concurrent_queue, ^{
NSLog(@"dispatch_group_1===%d===%@",i,[NSThread currentThread]);
});
}
dispatch_group_notify(group_t, dispatch_get_main_queue(), ^{
NSLog(@"轉(zhuǎn)主線程");
});
}
- (void)dispatch_group_2 {
dispatch_group_t groutp_t = dispatch_group_create();
for (int i = 0; i < 10; i++) {
dispatch_group_enter(groutp_t);
dispatch_async(self.concurrent_queue, ^{
NSLog(@"dispatch_group_2===%d===%@",i,[NSThread currentThread]);
dispatch_group_leave(groutp_t);
});
}
dispatch_group_notify(groutp_t, dispatch_get_main_queue(), ^{
NSLog(@"轉(zhuǎn)主線程");
});
}
- (void)dispatch_group_3 {
dispatch_group_t groutp_t = dispatch_group_create();
for (int i = 0; i < 10; i++) {
dispatch_group_enter(groutp_t);
dispatch_async(self.concurrent_queue, ^{
NSLog(@"dispatch_group_3===%d===%@",i,[NSThread currentThread]);
dispatch_group_leave(groutp_t);
});
}
dispatch_group_wait(groutp_t, DISPATCH_TIME_FOREVER);
NSLog(@"group end");
}
7廊谓、信號(hào)量的使用
GCD提供了信號(hào)量函數(shù),提供我們進(jìn)行線程安全操作舔示,當(dāng)信號(hào)量為0時(shí)等待操作电抚,信號(hào)量大于0是,繼續(xù)執(zhí)行操作俺祠,借帘,主要流程如下:
1肺然、創(chuàng)建信號(hào)量對(duì)象,確定信號(hào)初始值
2际起、開始執(zhí)行操作前檢查是否需要等待吐葱,執(zhí)行函數(shù)dispatch_semaphore_wait弟跑,如果當(dāng)前的信號(hào)量為0則阻塞防症,等待信號(hào)量大于0時(shí)繼續(xù)執(zhí)行,執(zhí)行函數(shù)完畢會(huì)使信號(hào)量減一饲嗽。當(dāng)一個(gè)任務(wù)執(zhí)行完畢時(shí)我們需要調(diào)用dispatch_semaphore_signal函數(shù)奈嘿,告訴系統(tǒng)一個(gè)任務(wù)執(zhí)行完畢,調(diào)用后會(huì)使信號(hào)量加1。dispatch_semaphore_wait和dispatch_semaphore_signal函數(shù)應(yīng)當(dāng)成對(duì)存在伯诬,有特別需求也可以不成對(duì)存在巫财。當(dāng)初始信號(hào)量為1時(shí),我們可以用作線程安全鎖進(jìn)行使用赫舒,代碼如下:
- (void)dispatch_semaphore {
dispatch_semaphore_t semaphore_t = dispatch_semaphore_create(3);
for (int i = 0; i < 10; i++) {
dispatch_semaphore_wait(semaphore_t, DISPATCH_TIME_FOREVER);
dispatch_async(self.concurrent_queue, ^{
int t = rand() % 5;
sleep(t);
NSLog(@"dispatch_semaphore===%d===%@===%d",i,[NSThread currentThread],t);
dispatch_semaphore_signal(semaphore_t);
});
}
NSLog(@"end");
}
8闽瓢、GCD的source事件
GCD可以監(jiān)聽事件源對(duì)象扣讼,創(chuàng)建事件源對(duì)象方法如下:
dispatch_source_t
dispatch_source_create(dispatch_source_type_t type,
uintptr_t handle,
uintptr_t mask,
dispatch_queue_t _Nullable queue);
事件源類型有很多種,如下所示:
// 自定義事件,觸發(fā)事件后的數(shù)據(jù)會(huì)被疊加運(yùn)算
#define DISPATCH_SOURCE_TYPE_DATA_ADD
// 自定義事件,觸發(fā)時(shí)間后的數(shù)據(jù)會(huì)被按位或運(yùn)算
#define DISPATCH_SOURCE_TYPE_DATA_OR
// 自定義事件,觸發(fā)時(shí)間后的數(shù)據(jù)會(huì)被替換
#define DISPATCH_SOURCE_TYPE_DATA_REPLACE
// 內(nèi)核端口發(fā)送數(shù)據(jù)事件
#define DISPATCH_SOURCE_TYPE_MACH_SEND
// 內(nèi)核端口接收數(shù)據(jù)事件
#define DISPATCH_SOURCE_TYPE_MACH_RECV
// 內(nèi)存壓力事件
#define DISPATCH_SOURCE_TYPE_MEMORYPRESSURE
// 進(jìn)程相關(guān)事件
#define DISPATCH_SOURCE_TYPE_PROC
// 讀文件相關(guān)事件
#define DISPATCH_SOURCE_TYPE_READ
// 寫文件相關(guān)事件
#define DISPATCH_SOURCE_TYPE_WRITE
// 信號(hào)相關(guān)事件
#define DISPATCH_SOURCE_TYPE_SIGNAL
// 定時(shí)器事件
#define DISPATCH_SOURCE_TYPE_TIMER
// 文件屬性修改事件
#define DISPATCH_SOURCE_TYPE_VNODE
創(chuàng)建事件源之后荔燎,我們需要設(shè)置回調(diào)函數(shù)销钝,如下:
void
dispatch_source_set_event_handler(dispatch_source_t source,
dispatch_block_t _Nullable handler);
注意創(chuàng)建的dispatch_source_t對(duì)象需要我們進(jìn)行強(qiáng)引用蒸健,持有對(duì)象婉商。
最后我們需要激活對(duì)象征讲,方法如下诗箍,傳入dispatch_source_t作為參數(shù)。
void
dispatch_activate(dispatch_object_t object);
定時(shí)器是我們常使用的一種任務(wù)邏輯滤祖,當(dāng)我們需要較高精度的定時(shí)器時(shí)我們可以使用GCD source的定時(shí)器執(zhí)行任務(wù)匠童,示例代碼如下:
- (void)dispatch_source_timer {
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.serial_queue);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"dispatch_source_timer %@",[NSThread currentThread]);
});
dispatch_activate(timer);
self.timer = timer;
}