Objective-C之GCD多線程(二)

前言

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í)行。如下圖所示

并行執(zhí)行的Serial Queue
利用dispatch_queue_create函數轉換成串行執(zhí)行

下面我們演示一下:

并行執(zhí)行的Serial Queue

利用dispatch_set_target_queue函數轉換為串行隊列:

轉換后變?yōu)榇嘘犃?/div>

dispatch_after

dispatch_afterAPI主要用于想要延遲執(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;
}
執(zhí)行結果

除了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_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í)行:

使用sync的Concurrent Dispatch Queue

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_apply執(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 高級編程》的學習筆記请琳。
  • 如有錯誤粱挡,歡迎指正。
  • 如需轉載俄精,請注明出處询筏。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市嘀倒,隨后出現的幾起案子屈留,更是在濱河造成了極大的恐慌局冰,老刑警劉巖测蘑,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異康二,居然都是意外死亡碳胳,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門沫勿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挨约,“玉大人,你說我怎么就攤上這事产雹〗氩眩” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵蔓挖,是天一觀的道長夕土。 經常有香客問我,道長,這世上最難降的妖魔是什么怨绣? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任角溃,我火速辦了婚禮,結果婚禮上篮撑,老公的妹妹穿的比我還像新娘减细。我一直安慰自己,他們只是感情好赢笨,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布未蝌。 她就那樣靜靜地躺著,像睡著了一般茧妒。 火紅的嫁衣襯著肌膚如雪树埠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天嘶伟,我揣著相機與錄音怎憋,去河邊找鬼。 笑死九昧,一個胖子當著我的面吹牛绊袋,可吹牛的內容都是我干的。 我是一名探鬼主播铸鹰,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼癌别,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蹋笼?” 一聲冷哼從身側響起展姐,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎剖毯,沒想到半個月后圾笨,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡逊谋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年擂达,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胶滋。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡板鬓,死狀恐怖,靈堂內的尸體忽然破棺而出究恤,到底是詐尸還是另有隱情俭令,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布部宿,位于F島的核電站抄腔,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜妓柜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一箱季、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧棍掐,春花似錦藏雏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至粟誓,卻和暖如春奏寨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鹰服。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工病瞳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人悲酷。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓套菜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親设易。 傳聞我的和親對象是個殘疾皇子逗柴,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內容