前言
在Objective-C之GCD多線程(一)中,我們了解了一些常見常用的GCD的API暮蹂。本文在前文的基礎上,再介紹一下幾個API:
- dispatch_set_target_queue
- dispatch_after
- Dispatch Group
- dispatch_barrier_async
- dispatch_sync
- dispatch_apply
- dispatch_suspend/dispatch_resume
- dispatch_Semaphore
- dispatch_once
- Dispatch I/O
dispatch_set_target_queue
dispatch_queue_create
函數生成的兩種Dispatch Queue的優(yōu)先級都與默認優(yōu)先級Global Dispatch Queue相同癌压。當我們需要改變由dispatch_queue_create
函數生成的Dispatch Queue的優(yōu)先級就需要用到dispatch_set_target_queue
函數了仰泻。例如,我們更改一個Serial Queue的優(yōu)先級為低優(yōu)先級:
// 創(chuàng)建了默認優(yōu)先級的Serial Queue
dispatch_queue_t serialQueue = dispatch_queue_create("com.larry.GcdTest", NULL);
// 獲取一個低優(yōu)先級Concurrent Queue
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
// 將Serial Queue優(yōu)先級變?yōu)榈? dispatch_set_target_queue(serialQueue, globalQueue);
dispatch_set_target
函數有2個參數:
- 第一個為需要轉換的隊列
- 第二個為目標隊列滩届,需要轉換的隊列在轉換后優(yōu)先級同目標隊列
利用dispatch_queue_create
函數我們還可以將多個并行執(zhí)行的Serial Queue轉換為串行執(zhí)行集侯,被轉換的隊列會被添加到目標隊列中串行執(zhí)行。如下圖所示
下面我們演示一下:
利用dispatch_set_target_queue函數轉換為串行隊列:
dispatch_after
dispatch_after
API主要用于想要延遲執(zhí)行處理帜消。例如:想在10秒后執(zhí)行響鈴棠枉,就可以使用dispatch_after
來實現。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
__block NSDate *sendDate=[NSDate date]; // 獲得當前時間
NSDateFormatter *dateformatter=[[NSDateFormatter alloc] init]; // 設置時間格式
[dateformatter setDateFormat:@"HH:mm"];
NSString *timeBegain = [dateformatter stringFromDate:sendDate];
NSLog(@"開始時間:%@",timeBegain);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
sendDate = [NSDate date];
NSString *timeEnd = [dateformatter stringFromDate:sendDate];
NSLog(@"鬧鐘響時間:%@",timeEnd);
});
while (1) {
// 防止程序結束
}
return 0;
}
運行結果:
可能心細的朋友會發(fā)現泡挺,不是延遲10秒執(zhí)行嗎术健?但是為什么圖片中不是10秒而是11秒?因為dispatch_after
是在指定時間之后將處理添加到queue中粘衬,但是在什么時候執(zhí)行需要看queue中的情況荞估!
dispatch_after
函數有2個參數咳促,第一個參數是由dispatch_time
函數生成的dispatch_time_t
類型的參數。dispatch_after
函數能從dispatch_time
函數的第一個參數中獲取開始時間勘伺,從第二個參數中獲取延遲執(zhí)行的時間跪腹;第二個參數是指定要追加操作的隊列;第三個參數則是需要執(zhí)行的Block飞醉。dispatch_time
函數中的第二個參數中的NSEC_PER_SEC
代表秒冲茸,還有一個NSEC_PER_MSEC
則代表毫秒。
Dispatch Group
當追加到Dispatch Queue中的多個處理全部執(zhí)行結束之后缅帘,我們通常都會需要執(zhí)行結束處理轴术。當使用Serial Queue的時候很簡單,只需要將全部處理添加到一個Serial Queue中钦无,結束處理在最后添加就可以實現逗栽。但是使用Concurrent Queue的時候,就很復雜失暂。所以我們需要利用Dispatch Group彼宠。
例如,追加3個處理到Global Queue(Concurrent Queue)中弟塞,在3個處理結束后執(zhí)行結束處理凭峡,我們可以這樣做:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
// 創(chuàng)建一個Serial Queue,用于執(zhí)行完成處理
dispatch_queue_t serialQueue = dispatch_queue_create("com.Larry.GCDTEST", NULL);
// 創(chuàng)建默認優(yōu)先級Concurrent Queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
// 創(chuàng)建Dispatch Group
dispatch_group_t group = dispatch_group_create();
// 添加操作
dispatch_group_async(group, concurrentQueue, ^{printf("處理1\n");});
dispatch_group_async(group, concurrentQueue, ^{printf("處理2\n");});
dispatch_group_async(group, concurrentQueue, ^{printf("處理3\n");});
// 操作完成執(zhí)行
dispatch_group_notify(group, serialQueue, ^{printf("處理全部完成\n");});
while (1) {
}
return 0;
}
除了dispatch_group_notify
函數可以判斷執(zhí)行完成外,我們還可以利用dispatch_group_wait
函數來進行判斷决记。
dispatch_group_wait
我們可以將以上代碼轉換為以下形式:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
// 創(chuàng)建一個Serial Queue,用于執(zhí)行完成處理
dispatch_queue_t serialQueue = dispatch_queue_create("com.Larry.GCDTEST", NULL);
// 創(chuàng)建默認優(yōu)先級Concurrent Queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
// 創(chuàng)建Dispatch Group
dispatch_group_t group = dispatch_group_create();
// 添加操作
dispatch_group_async(group, concurrentQueue, ^{printf("處理1\n");});
dispatch_group_async(group, concurrentQueue, ^{printf("處理2\n");});
dispatch_group_async(group, concurrentQueue, ^{printf("處理3\n");});
// 操作完成執(zhí)行
long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
if (result == 0) {
dispatch_async(serialQueue, ^{printf("處理結束");});
}
else{
// 處理沒結束的程序段
}
while (1) {
// 防止程序結束
}
return 0;
}
運行結果:
關于dispatch_group_wait
函數摧冀,有兩個參數和一個返回值。第一個參數是dispatch_group_t
類型系宫,我們需要傳遞需要進行完成處理的Group按价;第二個參數為dispatch_time_t
類型的變量。這個函數的返回值為0代表Group中的執(zhí)行結束笙瑟,不為0則代表指定時間到了楼镐,但是Group中的執(zhí)行還沒有完成。所以我們只需要對返回值進行判斷往枷,就能知道Group中的處理是否完成框产。上面的例子中,我們將時間設置為永久等待错洁,所以只有Group中的處理全部完成才會得到返回值秉宿,所以返回值的值恒為0。
我們可以根據需要來設置由時間是否到達還是處理是否全部完成來執(zhí)行收尾工作屯碴。相比較dispatch_group_notify
而言描睦,dispatch_group_wait
函數更靈活一些。
dispatch_barrier_async
我們在使用多線程的時候导而,在對數據進行處理的時候忱叭,很大的可能會碰到數據競爭的問題隔崎。如前文所述,我們可以使用Serial Dispatch Queue來避免這個問題韵丑。但是當對數據的讀取操作是分開的時候爵卒,也就是讀取處理和讀取處理并行執(zhí)行,那么使用Concurrent Dispatch Queue也是不會有問題的撵彻。當我們在多個讀取中遇到了一個寫入操作時钓株,該怎么辦?這個時候我們就可以使用dispatch_barrier_async
來解決這個問題陌僵。
在使用dispatch_barrier_async
函數的時候轴合,它會等待當前Concurrent Dispatch Queue中的處理執(zhí)行結束后,再將該處理追加到Concurrent Dispatch Queue碗短,也就是
dispatch_barrier_async
函數所攜帶的處理單獨占用Concurrent Dispatch Queue受葛。在執(zhí)行dispatch_barrier_async
函數的時候,它會屏蔽外界追加到Concurrent Dispatch Queue的操作豪椿。當它的處理執(zhí)行完成后奔坟,Concurrent Dispatch Queue才開始繼續(xù)正常添加操作携栋。如下圖所示:
dispatch_sync
async
意味著非同步搭盾,意思就是處理各自管各自的執(zhí)行;而與之相反sync
意味著同步婉支,需要一個接一個的執(zhí)行鸯隅。我們直接演示:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
// 創(chuàng)建一個Concurrent dispatch Queue
dispatch_queue_t concurrentdispatchQueue = dispatch_queue_create("com.larry.gcd.serialTest", DISPATCH_QUEUE_CONCURRENT);
// Serial dispatch Queue
dispatch_sync(concurrentdispatchQueue, ^{NSLog(@"1");});
dispatch_sync(concurrentdispatchQueue, ^{NSLog(@"2");});
dispatch_sync(concurrentdispatchQueue, ^{NSLog(@"3");});
dispatch_sync(concurrentdispatchQueue, ^{NSLog(@"4");});
dispatch_sync(concurrentdispatchQueue, ^{NSLog(@"5");});
dispatch_sync(concurrentdispatchQueue, ^{NSLog(@"6");});
// 防止程序結束
while (1) {
}
return 0;
}
Concurrent Dispatch Queue是并行執(zhí)行的,使用async的時候是亂序的向挖,但是使用了sync的時候就是一個接一個執(zhí)行:
dispatch_apply
dispatch_apply
函數是和Dispatch Group關聯的API蝌以,它的作用是按指定的次數將制定的Block追到制定的Dispatch Queue中,并等待全部處理執(zhí)行結束后才開始繼續(xù)往下走
該函數有三個參數何之,第一個為重復次數跟畅,第二個參數為追加對象的Dispatch Queue,第三個參數的Block為帶有參數的Blcok溶推,其參數相當于for循環(huán)中的i的作用徊件。所以我們可以用dispatch_apply來實現for循環(huán)的功能。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
// 創(chuàng)建一個Concurrent dispatch Queue
dispatch_queue_t concurrentdispatchQueue = dispatch_queue_create("com.larry.gcd.serialTest", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, concurrentdispatchQueue, ^(size_t index) {
NSLog(@"%zu",index);
});
NSLog(@"完成");
// 防止程序結束
// while (1) {
// }
return 0;
}
執(zhí)行結果:
dispatch_suspend/dispatch_resume
這兩個函數前者用來掛起隊列蒜危,隊列掛起后虱痕,追加到隊列中但還沒有開始執(zhí)行的處理,將暫停執(zhí)行辐赞;后者用來恢復隊列部翘,恢復隊列后,暫停的處理繼續(xù)執(zhí)行响委。使用方式如下:
dispatch_suspend(Queue)// 掛起隊列
dispatch_resume(Queue)// 暫停隊列
在前文提到的dispatch_set_target_queue
函數中新思,目標隊列如果被掛起窖梁,那么被轉換的隊列也會相同的掛起。但是被轉換的隊列被掛起,目標隊列則不受影響嗅回!
Dispatch Semaphore
Dispatch Semaphore和操作系統(tǒng)原理中的信號量一樣侥蒙,都是用來避免數據競爭這一類問題的。前文講到過dispatch_barrier_async
這個函數彰导,但是此函數對數據競爭處理的對象的粒度更細。
Dispatch Semaphore是持有計數的信號敲茄,當它的計數大于1時位谋,就會對其進行減1并執(zhí)行處理;當計數為0或小于0的時候堰燎,就會等待下去掏父。更加詳細的請自行參閱:《操作系統(tǒng)原理》。
通常使用Dispatch Semaphore的時候會使用以下函數:
函數 | 作用 |
---|---|
dispatch_semaphore_create | 創(chuàng)建信號量 |
dispatch_semaphore_wait | 此函數對信號量進行控制秆剪,當信號量值大于等于1的時候減1赊淑,然后返回;當小于等于0的時候仅讽,則在設置的時間內等待 |
dispatch_semaphore_signal | 將信號量加1 |
下面舉例使用:
##import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
// 創(chuàng)建一個Concurrent dispatch Queue
dispatch_queue_t concurrentdispatchQueue = dispatch_queue_create("com.larry.gcd.serialTest", DISPATCH_QUEUE_CONCURRENT);
// 提供一個信號量陶缺,也就意味著同時只有1個線程能對資源進行操作
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i =0; i<1000; ++i) {
dispatch_async(concurrentdispatchQueue, ^{
// 對信號量資源進行判斷,當信號量大于等于1的時候洁灵,將信號量減一(相當于消耗一個資源)饱岸,并返回,設置時間為永遠等待
// 如果此函數沒有返回徽千,則阻塞在這里苫费,等待資源。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:[NSNumber numberWithInt:i]];
// 處理完成双抽,信號量加一(相當于釋放一個資源)
dispatch_semaphore_signal(semaphore);
});
}
return 0;
}
我們將for循環(huán)執(zhí)行1000次百框,如果沒有使用Dispatch Semaphore來限制資源,那么同時對arry訪問的的操作就很多牍汹,會導致程序異常結束铐维,但是用Dispatch Semaphore對資源進行限制的時候,程序就能穩(wěn)定的進行下去柑贞。
dispatch_once
dispatch_once
函數是保證在應用程序執(zhí)行中只執(zhí)行一次指定處理的API方椎。通常在建立單例中使用。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 初始化單例
});
Dispatch I/O
在讀取較大文件的時候將文件切割開來钧嘶,Dispatch I/O使用Global Dispatch Queue來同步讀取,加快讀取速度棠众。利用dispatch_io_set_low_water
來設置一次讀取的大小,dispatch_io_read
函數使用Global Dispatch Queue來并列讀取,當讀取結束后會回調Block中的代碼進行相關資源合并闸拿。
請看下面蘋果中使用Dispatch I/O 和 Dispatch Data的例子空盼。下面的代碼摘自Apple System Log API里的源代碼
// 創(chuàng)建串行隊列
pipe_q = dispatch_queue_create("PipeQ", NULL);
// 創(chuàng)建 Dispatch I/O
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
close(fd);
});
*out_fd = fdpair[1];
// 該函數設定一次讀取的大小(分割大行禄纭)
dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
//
dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
if (err == 0) // err等于0 說明讀取無誤
{
// 讀取完“單個文件塊”的大小
size_t len = dispatch_data_get_size(pipedata);
if (len > 0)
{
// 定義一個字節(jié)數組bytes
const charchar *bytes = NULL;
charchar *encoded;
dispatch_data_t md = dispatch_data_create_map(pipedata, (const voidvoid **)&bytes, &len);
encoded = asl_core_encode_buffer(bytes, len);
asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded);
free(encoded);
_asl_send_message(NULL, merged_msg, -1, NULL);
asl_msg_release(merged_msg);
dispatch_release(md);
}
}
if (done)
{
dispatch_semaphore_signal(sem);
dispatch_release(pipe_channel);
dispatch_release(pipe_q);
}
dispatch_io_create
函數生成Dispatch I/O,并指定發(fā)生error時用來執(zhí)行處理的block揽趾,以及執(zhí)行該block的Dispatch Queue。
dispatch_io_set_low_water
函數設置一次讀取的大小
dispatch_io_read
函數使用Global Dispatch Queue 開始并發(fā)讀取苛骨。其中第四個參數是后面Block執(zhí)行的隊列篱瞎,相當于函數是在全局隊列中使用串行隊列。(此處我也不是很明白痒芝,歡迎有其他見解的朋友發(fā)表看法)俐筋。每當各個分割的文件塊讀取結束時,將含有文件塊數據的 Dispatch Data(這里指pipedata) 傳遞給 dispatch_io_read
函數指定的讀取結束時回調用的block严衬,這個block拿到每一塊讀取好的Dispatch Data(這里指pipe data)澄者,然后進行合并處理。
結語
- 本文主要講常用的一些GCD的API.
- 此為《Objective-C 高級編程》的學習筆記请琳。
- 如有錯誤粱挡,歡迎指正。
- 如需轉載俄精,請注明出處询筏。
- 文/潘曉璐 我一進店門沫勿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挨约,“玉大人,你說我怎么就攤上這事产雹〗氩眩” “怎么了?”我有些...
- 正文 為了忘掉前任角溃,我火速辦了婚禮,結果婚禮上篮撑,老公的妹妹穿的比我還像新娘减细。我一直安慰自己,他們只是感情好赢笨,可當我...
- 文/花漫 我一把揭開白布未蝌。 她就那樣靜靜地躺著,像睡著了一般茧妒。 火紅的嫁衣襯著肌膚如雪树埠。 梳的紋絲不亂的頭發(fā)上,一...
- 文/蒼蘭香墨 我猛地睜開眼癌别,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蹋笼?” 一聲冷哼從身側響起展姐,我...
- 正文 年R本政府宣布部宿,位于F島的核電站抄腔,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜妓柜,卻給世界環(huán)境...
- 文/蒙蒙 一箱季、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧棍掐,春花似錦藏雏、人聲如沸。這莊子的主人今日做“春日...
- 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至粟誓,卻和暖如春奏寨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鹰服。 一陣腳步聲響...
推薦閱讀更多精彩內容
- 背景 擔心了兩周的我終于輪到去醫(yī)院做胃鏡檢查了!去的時候我都想好了最壞的可能(胃癌)顿肺,之前在網上查的癥狀都很相似戏溺。...
- 3.1 Grand Central Dispatch(GCD)概要 3.1.1 什么是CGD Grand Cent...
- 本篇博客共分以下幾個模塊來介紹GCD的相關內容: 多線程相關概念 多線程編程技術的優(yōu)缺點比較旷祸? GCD中的三種隊列...