GCD(Grand Central Dispatch)是一套相對底層的C語言API接口,用來在多核硬件上進行多線程編程,是iOS中應(yīng)用最廣的多線程編程技術(shù)。使用GCD時,開發(fā)者無需關(guān)心線程的管理,GCD會自動維護一個線程池,開發(fā)者只需要把要執(zhí)行的任務(wù),按需分配到不同的隊列中即可。
一、GCD調(diào)度機制
調(diào)度隊列是GCD中非常重要的一個概念,執(zhí)行多線程任務(wù)實際是由調(diào)度隊列完成的,開發(fā)者只需要將任務(wù)添加到合適的調(diào)度隊列中即可。
GCD中的調(diào)度隊列有3中類型:主隊列、全局隊列、自定義隊列。
1、主隊列
主隊列中的任務(wù),都將在主線程中執(zhí)行。
在應(yīng)用程序中,主線程僅有1個,因此主隊列是一個簡單的串行隊列,其中的任務(wù)會在主線程中一次執(zhí)行。使用函數(shù)dispatch_get_main_queue()可以獲取主隊列。
2、全局隊列
全局隊列是由系統(tǒng)定義的一組任務(wù)隊列,均為并行隊列,其中的任務(wù)會并行執(zhí)行,但是執(zhí)行的順序會嚴(yán)格遵循FIFO(先進先出)策略。使用下面的函數(shù)可以獲取全局隊列:
dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);
其中第1個參數(shù)identifier用來指定要獲取的全局隊列標(biāo)識,對應(yīng)不同優(yōu)先級:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 優(yōu)先級高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 優(yōu)先級中
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 優(yōu)先級低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺的全局隊列,優(yōu)先級最低
第2個參數(shù)flags是GCD預(yù)留參數(shù),目前無實質(zhì)意義,傳0即可。
3、自定義隊列
GCD支持開發(fā)者根據(jù)需要,創(chuàng)建自定義隊列。自定義隊列可以是串行,也可以是并行。若是串行隊列,放入其中的任務(wù)會一次執(zhí)行,并行隊列,則不會按照順序,而是會直接執(zhí)行。創(chuàng)建自定義隊列的函數(shù)如下:
dispatch_queue_t;
dispatch_queue_create(const char * _Nullable label, dispatch_queue_attr_t _Nullable attr);
其中第1個參數(shù)label指定隊列的名稱,第2個參數(shù)attr指定隊列的類型,定義如下:
DISPATCH_QUEUE_SERIAL // 串行隊列
DISPATCH_QUEUE_CONCURRENT // 并行隊列
示例:
dispatch_queue_t queue =dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
二、添加任務(wù)到GCD隊列
1、常用的向隊列中添加任務(wù)的函數(shù)有如下兩個:
// 添加與當(dāng)前線程同步的任務(wù)
dispatch_sync(dispatch_queue_t _Nonnull queue, ^(void)block);
// 添加與當(dāng)前線程異步的任務(wù)
dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block);
這兩個函數(shù)也是GCD多線程編程的核心。示例:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"%@:1", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"%@:2", [NSThread currentThread]);
});
}
運行代碼,控制臺輸出如下:
2021-11-20 10:53:30.398419+0800 MyProject[19214:1253521] <NSThread: 0x600002318680>{number = 1, name = main}:1
2021-11-20 10:53:30.398642+0800 MyProject[19214:1255254] <NSThread: 0x60000232f3c0>{number = 7, name = (null)}:2
可見,dispatch_sync函數(shù)指定的任務(wù)雖然在自定義隊列中執(zhí)行,但是其設(shè)定為與當(dāng)前線程同步,因此也被調(diào)度到主線程中執(zhí)行。
2、執(zhí)行順序
dispatch_queue_t serialQueue = dispatch_queue_create("MyQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(serialQueue, ^{
// 異步任務(wù)
NSLog(@"2");
});
NSLog(@"3");
dispatch_sync(serialQueue, ^{
// 同步任務(wù)
NSLog(@"4");
});
NSLog(@"5");
打印順序為 13245 。
首先打印1;
接下來將任務(wù)2添加到串行隊列上,由于任務(wù)2是異步的,不會阻塞線程,所以繼續(xù)向下執(zhí)行,打印3;
然后執(zhí)行到任務(wù)4,將任務(wù)4添加到該串行隊列上,根據(jù)隊列FIFO(先進先出)原則,任務(wù)4需等任務(wù)2執(zhí)行后才會執(zhí)行,又因為任務(wù)4是同步的,會阻塞線程,所以只有任務(wù)4完成后才會繼續(xù)向下執(zhí)行,打印5。
這里任務(wù)4在主線程中執(zhí)行,任務(wù)2在子線程中執(zhí)行。
如果任務(wù)4是添加到與任務(wù)2不同的隊列(串行或并行),則任務(wù)2和4無序執(zhí)行。
總結(jié):串行隊列先異步后同步。
三、使用調(diào)度組
調(diào)度組是GCD中基于信號量更高一層的封裝功能,使用調(diào)度組可以將某些任務(wù)綁定在一起,無論這些任務(wù)是在串行還是并行,也無論是否在同一個線程中,調(diào)度組都可以保證其按照開發(fā)者逾期的順序去執(zhí)行。
比如,我們常會遇到依賴于兩個或更多個網(wǎng)絡(luò)請求的返回值,去進行數(shù)據(jù)的處理或UI展示。我們模擬有兩個自定義的串行隊列,兩個耗時任務(wù)A和B分別在其中執(zhí)行,當(dāng)A、B兩個任務(wù)都執(zhí)行完成后再執(zhí)行任務(wù)C,任務(wù)A、B完成的順序并不確定,此時就非常適合使用調(diào)度組。首先將任務(wù)A、B綁定到同一個調(diào)度組中,等待調(diào)度組中的所有任務(wù)執(zhí)行完成后,再執(zhí)行任務(wù)C,示例代碼如下:
dispatch_queue_t queue1 = dispatch_queue_create("myQueue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("myQueue2", DISPATCH_QUEUE_SERIAL);
dispatch_group_t group = dispatch_group_create(); // 創(chuàng)建調(diào)度組
// 綁定任務(wù)到調(diào)度組
dispatch_group_async(group, queue1, ^{
// 耗時任務(wù)A
[NSThread sleepForTimeInterval:1];
NSLog(@"任務(wù)A完成");
});
dispatch_group_async(group, queue2, ^{
// 耗時任務(wù)B
[NSThread sleepForTimeInterval:2];
NSLog(@"任務(wù)B完成");
});
// 阻塞線程,直到隊列中的任務(wù)執(zhí)行完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// 任務(wù)C
NSLog(@"任務(wù)C完成");
dispatch_group_wait()函數(shù)的作用是阻塞當(dāng)前線程,等待調(diào)度組中所有任務(wù)完成后,再向下執(zhí)行,其中第1個參數(shù)為調(diào)度組對象,第二個參數(shù)則是設(shè)置最長等待時間。DISPATCH_TIME_FOREVER表示一直等待,直至調(diào)度組任務(wù)完成。如果要設(shè)置為最長等待5s,改為:
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * (int64_t)1000000000)); // 單位為納秒
dispatch_group_wait()函數(shù)會阻塞當(dāng)前線程進行等待,在實際開發(fā)中,很少會用到。更多的場景是最后要執(zhí)行的任務(wù)也是一個耗時任務(wù)。例如有A、B、C三個耗時任務(wù),都在并行的隊列中執(zhí)行,但是C必須依賴于A和B先完成,可以使用dispatch_group_notify()函數(shù),示例代碼如下:
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
// 耗時任務(wù)A
[NSThread sleepForTimeInterval:2];
NSLog(@"任務(wù)A完成");
});
dispatch_group_async(group, queue, ^{
// 耗時任務(wù)B
[NSThread sleepForTimeInterval:2];
NSLog(@"任務(wù)B完成");
});
dispatch_group_notify(group, queue, ^{
// 耗時任務(wù)C
[NSThread sleepForTimeInterval:2];
NSLog(@"任務(wù)C完成");
});
// 任務(wù)D
NSLog(@"任務(wù)D完成");
dispatch_group_notify()函數(shù)的作用是當(dāng)調(diào)度組中的任務(wù)都執(zhí)行完成后,再執(zhí)行指定的任務(wù)。
上面的例子中,耗時任務(wù)都是用線程休眠模擬的。實際開發(fā)中,遇到更多的場景是耗時任務(wù)本身就是異步的,這時還需要配合dispatch_group_enter()和dispatch_group_leave(),示例代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
[self asyncTask1:^{
dispatch_group_leave(group);
}];
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
[self asyncTask2:^{
dispatch_group_leave(group);
}];
});
dispatch_group_notify(group, queue, ^{
NSLog(@"任務(wù)完成");
});
}
- (void)asyncTask1:(void (^)(void))block {
// 異步任務(wù)1
block();
}
- (void)asyncTask2:(void (^)(void))block {
// 異步任務(wù)2
block();
}
dispatch_group_enter()函數(shù)告訴調(diào)度組,即將有一個任務(wù)開始執(zhí)行,dispatch_group_leave()函數(shù)告訴調(diào)度組,有一個任務(wù)執(zhí)行完成。這兩個函數(shù)必須成對使用。
四、使用GCD進行快速迭代
常用的循環(huán)方式如while循環(huán)、do-while循環(huán)、for循環(huán),以及更加快速的for-in循環(huán),都是在當(dāng)前線程中執(zhí)行的,哪怕是多和CPU設(shè)備,其調(diào)用的資源仍然是單核的。GCD提供了一種更加高效的循環(huán)方法,那就是使用dispatch_apply()函數(shù),可以最大限度的利用多核CPU的優(yōu)勢,GCD會自動分配線程來執(zhí)行迭代任務(wù)。示例:
NSArray *array = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g"];
dispatch_apply(array.count , dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(size_t iteration) {
NSLog(@"%@ || %zu:%@", [NSThread currentThread], iteration, array[iteration]);
});
dispatch_apply()函數(shù)中第1個參數(shù)為要迭代的次數(shù),第2個為執(zhí)行任務(wù)的隊列,第3個參數(shù)則為要執(zhí)行的迭代任務(wù)。
由于dispatch_apply()函數(shù)中迭代任務(wù)分別被分配到不同線程中執(zhí)行,所以其執(zhí)行順序是不可預(yù)測的。而且該函數(shù)是同步的,會阻塞當(dāng)前線程,如果需要異步執(zhí)行,就將其放入到另一個非主隊列中執(zhí)行。
五、使用GCD監(jiān)聽事件源
監(jiān)聽事件源是指當(dāng)某些事件發(fā)生時,在指定的隊列中執(zhí)行回調(diào)任務(wù)。在實際應(yīng)用中,使用GCD的事件源可以方便地創(chuàng)建自動聚合的自定義事件以及精度更高的定時器。
使用下面的函數(shù)創(chuàng)建事件源:
dispatch_source_t;
dispatch_source_create(dispatch_source_type_t _Nonnull type, uintptr_t handle, uintptr_t mask, dispatch_queue_t _Nullable queue);
第1個參數(shù)type指定事件源的類型;第2個參數(shù)handle為事件句柄,取決于第1個參數(shù)的實踐類型,例如如果是內(nèi)核端口事件,則將這個參數(shù)設(shè)置為端口號;第3個參數(shù)mask也取決于第1個參數(shù)的事件類型,例如如果是文件操作相關(guān)的事件,則將這個參數(shù)設(shè)置為要監(jiān)聽的文件屬性;第4個參數(shù)queue設(shè)置執(zhí)行事件回調(diào)任務(wù)的隊列。事件源類型的定義如下:
// 自定義事件,觸發(fā)事件后的數(shù)據(jù)會被疊加運算
#define DISPATCH_SOURCE_TYPE_DATA_ADD
// 自定義事件,觸發(fā)事件后的數(shù)據(jù)會被按位或運算
#define DISPATCH_SOURCE_TYPE_DATA_OR
// 自定義事件,觸發(fā)事件后的數(shù)據(jù)會被替換
#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
// 進程相關(guān)事件
#define DISPATCH_SOURCE_TYPE_PROC
// 讀文件相關(guān)事件
#define DISPATCH_SOURCE_TYPE_READ
// 信號相關(guān)事件
#define DISPATCH_SOURCE_TYPE_SIGNAL
// 定時器事件
#define DISPATCH_SOURCE_TYPE_TIMER
// 文件屬性修改事件
#define DISPATCH_SOURCE_TYPE_VNODE
// 寫文件相關(guān)事件
#define DISPATCH_SOURCE_TYPE_WRITE
創(chuàng)建了事件源后,還需要對齊設(shè)置一個回調(diào)任務(wù)。當(dāng)事件發(fā)生時,會在指定的隊列中執(zhí)行回調(diào)任務(wù)。設(shè)置回調(diào)任務(wù)的函數(shù)如下:
dispatch_source_set_event_handler(dispatch_source_t _Nonnull source, ^(void)handler);
實際開發(fā)中,事件源在兩種場景下應(yīng)用最為廣泛。當(dāng)某個事件會頻繁發(fā)生,我們需要將其聚合進行處理時,可以進行自定義事件源的監(jiān)聽。例如,某個頁面由多個數(shù)據(jù)源控制,當(dāng)其中的數(shù)據(jù)源變化時需要刷新頁面,同時要避免數(shù)據(jù)源頻繁變化導(dǎo)致多次刷新而影響其性能。要解決這個問題,可以使用自定義事件,示例:
// 創(chuàng)建事件源
dispatch_source_t source_t = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_REPLACE, 0, 0, dispatch_get_main_queue());
// 設(shè)置回調(diào)任務(wù)
dispatch_source_set_event_handler(source_t, ^{
NSLog(@"接收到自定義事件%lu", dispatch_source_get_data(source_t));
});
// 激活事件源監(jiān)聽
dispatch_resume(source_t);
for (int i = 0; i < 10; i++) {
// 合并自定義事件源的數(shù)據(jù)
dispatch_source_merge_data(source_t, 1);
}
示例中雖然在for循環(huán)中調(diào)用了10次事件合并,但最終只觸發(fā)了一次回調(diào)任務(wù)。
使用定時器事件源可以創(chuàng)建精度更高的定時器,示例代碼如下:
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"定時器%@", timer);
});
dispatch_resume(timer);
dispatch_source_set_timer ()函數(shù)用來設(shè)置定時器事件的回調(diào),第1個參數(shù)為定時器事件源對象,第2個參數(shù)為定時器任務(wù)的執(zhí)行時間間隔,第3個參數(shù)為延時多久后開始執(zhí)行。
六、Dispatch Semaphore - 信號量
GCD中的調(diào)度組實際上是基于信號量的封裝,信號量本身的作用是通過信號來觸發(fā)任務(wù)的執(zhí)行。在GCD中,與信號量有關(guān)的函數(shù)只有3個:
1、dispatch_semaphore_create()
創(chuàng)建一個Semaphore并初始化信號的總量;
2、dispatch_semaphore_signal()
發(fā)送一個信號,使信號總量加1
3、dispatch_semaphore_wait()
使信號總量減1,當(dāng)信號總量為0時,就會一直等待(阻塞所在線程),否則就可以正常執(zhí)行。
示例如下:
// 創(chuàng)建信號量,參數(shù)為信號量初始值
dispatch_semaphore_t semaphore_t = dispatch_semaphore_create(0);
// 發(fā)送信號,會使指定的信號量值加1
dispatch_semaphore_signal(semaphore_t);
while (1) {
/**
阻塞函數(shù)
當(dāng)信號量大于0時,會穿透阻塞函數(shù)往后執(zhí)行,并使信號量 減1
當(dāng)信號量為0時,會一直阻塞
可以通過函數(shù)的地2個參數(shù)設(shè)置阻塞的超時時長
*/
dispatch_semaphore_wait(semaphore_t, DISPATCH_TIME_FOREVER);
NSLog(@"%d", count++);
}
Dispatch Semaphore在實際開發(fā)中主要用于以下2點:
1、保持線程同步,將異步執(zhí)行的任務(wù)轉(zhuǎn)換為同步執(zhí)行
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSInteger num = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
num = 666;
// 解鎖
dispatch_semaphore_signal(semaphore);
});
// wait函數(shù) 加鎖
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore end, num = %zd", num);
dispatch_semaphore_wait阻塞了當(dāng)前線程,當(dāng)dispatch_semaphore_signal解鎖后,當(dāng)前線程才繼續(xù)執(zhí)行。此處輸出num為666,如果不加鎖,則輸出0。
2、保證線程安全,為線程加鎖
在線程安全中,可以將dispatch_semaphore_wait看作加鎖,dispatch_semaphore_signal看作解鎖。
創(chuàng)建全局變量:
_semaphore = dispatch_semaphore_create(1); // 注意! 初始信號量為1
_count = 0;
創(chuàng)建異步任務(wù):
- (void)asyncTask {
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
_count++;
sleep(1);
NSLog(@"執(zhí)行任務(wù):%d", _count);
dispatch_semaphore_signal(_semaphore);
}
異步并發(fā)調(diào)用asyncTask:
for (int i = 0; i < 100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self asyncTask];
});
}
打印結(jié)果為從1順序執(zhí)行到100,不存在兩個任務(wù)同事執(zhí)行的情況。
在子線程中并發(fā)執(zhí)行asyncTask,那么第一個添加到并發(fā)隊列里的,會將信號量減1,此時信號量為0,可以執(zhí)行接下來的任務(wù)。而并發(fā)隊列中其他任務(wù),由于此時信號量不等于0,必須等當(dāng)前正在執(zhí)行的任務(wù)執(zhí)行完畢后,調(diào)用dispatch_semaphore_signal將信號量加1,才能繼續(xù)執(zhí)行接下來的任務(wù),以此類推,從而達到線程加鎖的目的。
七、執(zhí)行延時任務(wù)
GCD提供的dispatch_after()函數(shù)可以讓我們添加進隊列的任務(wù)延時執(zhí)行,并且這個函數(shù)本身是異步的。該函數(shù)并不是在指定時間后執(zhí)行,而只是在指定時間后追加處理到dispatch_queue。
示例如下:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"延時任務(wù)");
});
dispatch_after(dispatch_time_t when, dispatch_queue_t _Nonnull queue, ^(void)block)函數(shù)第1個參數(shù)when設(shè)置執(zhí)行任務(wù)的時刻,第2個參數(shù)queue設(shè)置執(zhí)行任務(wù)的隊列,第3個參數(shù)block設(shè)置要執(zhí)行的任務(wù)。
由于其內(nèi)部使用的是dispatch_time_t管理時間,而不是NSTimer,所以如果在子線程中調(diào)用,相比performSelector:afterDelay,不需要關(guān)心RunLoop是否開啟。
八、柵欄函數(shù)
GCD中的柵欄函數(shù)可以在并行隊列中使某段邏輯獨立執(zhí)行。在并行隊列中有需要保證線程安全的執(zhí)行任務(wù)時,使用柵欄函數(shù)非常方便。
例如,某個并行任務(wù)隊列專門用來處理數(shù)據(jù)的讀寫任務(wù)。對于讀任務(wù),可以多個任務(wù)并行執(zhí)行;對于寫任務(wù),需要保證其獨立性,即在執(zhí)行寫邏輯時,不能有讀的任務(wù)以及其他寫任務(wù)在執(zhí)行。編寫如下測試代碼:
dispatch_queue_t queue = dispatch_queue_create("MyQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 4; i++) {
NSLog(@"讀任務(wù)1:%d", i);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 4; i++) {
NSLog(@"讀任務(wù)2:%d", i);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 4; i++) {
NSLog(@"讀任務(wù)3:%d", i);
}
});
運行代碼,控制臺輸出如下:
2021-12-22 16:42:04.779427+0800 MyProject[24792:295858] 讀任務(wù)2:0
2021-12-22 16:42:04.779429+0800 MyProject[24792:295870] 讀任務(wù)1:0
2021-12-22 16:42:04.779432+0800 MyProject[24792:295863] 讀任務(wù)3:0
2021-12-22 16:42:04.779593+0800 MyProject[24792:295870] 讀任務(wù)1:1
2021-12-22 16:42:04.779601+0800 MyProject[24792:295858] 讀任務(wù)2:1
2021-12-22 16:42:04.779605+0800 MyProject[24792:295863] 讀任務(wù)3:1
2021-12-22 16:42:04.779753+0800 MyProject[24792:295858] 讀任務(wù)2:2
2021-12-22 16:42:04.779756+0800 MyProject[24792:295870] 讀任務(wù)1:2
2021-12-22 16:42:04.779767+0800 MyProject[24792:295863] 讀任務(wù)3:2
2021-12-22 16:42:04.779891+0800 MyProject[24792:295858] 讀任務(wù)2:3
2021-12-22 16:42:04.780036+0800 MyProject[24792:295870] 讀任務(wù)1:3
2021-12-22 16:42:04.780726+0800 MyProject[24792:295863] 讀任務(wù)3:3
可以看出,3個讀任務(wù)是并行執(zhí)行的。此時如果直接添加寫任務(wù),那么讀寫任務(wù)將并行執(zhí)行,造成線程安全問題。使用柵欄函數(shù)的代碼如下:
dispatch_queue_t queue = dispatch_queue_create("MyQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 4; i++) {
NSLog(@"讀任務(wù)1:%d", i);
}
});
dispatch_barrier_async(queue, ^{
for (int i = 0; i < 4; i++) {
NSLog(@"寫任務(wù)1:%d", i);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 4; i++) {
NSLog(@"讀任務(wù)2:%d", i);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 4; i++) {
NSLog(@"讀任務(wù)3:%d", i);
}
});
再次運行代碼,控制臺輸出如下:
2021-12-22 16:46:31.061717+0800 MyProject[24953:299357] 讀任務(wù)1:0
2021-12-22 16:46:31.061888+0800 MyProject[24953:299357] 讀任務(wù)1:1
2021-12-22 16:46:31.062022+0800 MyProject[24953:299357] 讀任務(wù)1:2
2021-12-22 16:46:31.062151+0800 MyProject[24953:299357] 讀任務(wù)1:3
2021-12-22 16:46:31.062302+0800 MyProject[24953:299357] 寫任務(wù)1:0
2021-12-22 16:46:31.062449+0800 MyProject[24953:299357] 寫任務(wù)1:1
2021-12-22 16:46:31.062600+0800 MyProject[24953:299357] 寫任務(wù)1:2
2021-12-22 16:46:31.062738+0800 MyProject[24953:299357] 寫任務(wù)1:3
2021-12-22 16:46:31.062992+0800 MyProject[24953:299357] 讀任務(wù)2:0
2021-12-22 16:46:31.063001+0800 MyProject[24953:299361] 讀任務(wù)3:0
2021-12-22 16:46:31.063780+0800 MyProject[24953:299357] 讀任務(wù)2:1
2021-12-22 16:46:31.064288+0800 MyProject[24953:299361] 讀任務(wù)3:1
2021-12-22 16:46:31.064834+0800 MyProject[24953:299357] 讀任務(wù)2:2
2021-12-22 16:46:31.065189+0800 MyProject[24953:299361] 讀任務(wù)3:2
2021-12-22 16:46:31.065551+0800 MyProject[24953:299357] 讀任務(wù)2:3
2021-12-22 16:46:31.065938+0800 MyProject[24953:299361] 讀任務(wù)3:3
dispatch_barrier_async()函數(shù)就像一個柵欄,一旦使用柵欄函數(shù)添加了任務(wù),之后再添加的并行任務(wù)會被阻塞,等待已經(jīng)執(zhí)行的任務(wù)執(zhí)行完成后單獨執(zhí)行柵欄函數(shù)設(shè)置的任務(wù),當(dāng)柵欄函數(shù)設(shè)置的任務(wù)執(zhí)行完成后,才會繼續(xù)執(zhí)行后面添加的任務(wù),保證了柵欄函數(shù)指定任務(wù)執(zhí)行的獨立性和安全性。
dispatch_barrier_async()函數(shù)與當(dāng)前線程異步執(zhí)行。
dispatch_barrier_sync()函數(shù)與當(dāng)前線程同步執(zhí)行。
可以利用柵欄函數(shù)實現(xiàn)多讀單寫,示例如下:
- (id)readDataForKey:(NSString *)key {
__block id result;
dispatch_async(_queue, ^{
result = [self valueForKey:key];
});
return result;
}
- (void)writeData:(id)data forKey:(NSString *)key {
dispatch_barrier_async(_queue, ^{
[self setValue:data forKey:key];
});
}
九、單例實現(xiàn)
+ (instancetype)shareInstance {
static dispatch_once_t onceToken;
static id instance = nil;
dispatch_once(&onceToken, ^{
// 代碼只會執(zhí)行一次,線程安全
instance = [[self alloc] init];
});
return instance;
}