GCD整理(二)

這篇會(huì)整理GCD常用的API

目錄
1、dispatch_after
2、dispatch_apply
3、dispatch_barrier_async
4、Dispatch Semaphore
5、Dispatch Group

dispatch_after

看下面一段代碼:

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
    NSLog(@"hey");
});

dispatch_time_t類型變量作為dispatch_after函數(shù)的第一個(gè)參數(shù),可以由dispatch_time和dispatch_walltime函數(shù)創(chuàng)建。先來(lái)說(shuō)前者。

dispatch_time函數(shù)接收兩個(gè)參數(shù),它會(huì)返回從第一個(gè)參數(shù)指定的時(shí)間開始,到第二個(gè)參數(shù)指定的以毫微秒為單位的時(shí)間間隔后的時(shí)間。比如上面代碼中就是從現(xiàn)在開始,3秒后的時(shí)間。

dispatch_after會(huì)將任務(wù)在指定時(shí)間后加入到執(zhí)行隊(duì)列,n * NSEC_PER_SEC會(huì)得到一個(gè)單位是毫微秒的數(shù)值,要表達(dá)3秒需要寫成3 * NSEC_PER_SEC,直接寫3是不行的。如果需要表示毫秒,可以使用NSEC_PER_MSEC,比如100毫秒寫成100 * NSEC_PER_MSEC。

dispatch_walltime用于指定絕對(duì)時(shí)間,比如要指定時(shí)間為2011年11月11日11時(shí)11分11秒,可以使用如下方法做NSDate到dispatch_time_t的轉(zhuǎn)換。

+ (dispatch_time_t)getDispatchTimeByDate:(NSDate *)date{
    NSTimeInterval interval;
    CGFloat second, subsecond;
    struct timespec time;
    dispatch_time_t milestone;
    
    interval = [date timeIntervalSince1970];
    subsecond = modf(interval, &second);
    time.tv_sec = second;
    time.tv_nsec = subsecond * NSEC_PER_SEC;
    milestone = dispatch_walltime(&time, 0);
    return milestone;
}

關(guān)于dispatch_after方法的參數(shù):

dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block) 參數(shù)1:執(zhí)行的時(shí)間。參數(shù)2:執(zhí)行的隊(duì)列。參數(shù)3:任務(wù)。

另外,因?yàn)閐ispatch_after并不是在指定時(shí)間后執(zhí)行,而是指定時(shí)間后加入隊(duì)列,所示任務(wù)執(zhí)行的具體時(shí)間未必會(huì)準(zhǔn)確按照time參數(shù)。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC);
int i = 1;
while (i < 1000000000) {
    i++;
}
dispatch_after(time, dispatch_get_main_queue(), ^{
    NSLog(@"hey");
});

比如這段代碼,因?yàn)檠h(huán)和log都在主線程執(zhí)行,循環(huán)會(huì)執(zhí)行很久,所以0.5秒后輸出語(yǔ)句并沒(méi)有執(zhí)行,只是加入到隊(duì)列中,等到循環(huán)結(jié)束才輸出“hey”。

如果只是簡(jiǎn)單的使用可以直接使用系統(tǒng)提供的代碼塊來(lái)快速生成代碼。

dispatch_after代碼塊.png

dispatch_apply

dispatch_apply會(huì)按照給定的次數(shù)在指定隊(duì)列中重復(fù)執(zhí)行任務(wù),注意它是dispatch_sync和Dispatch Group的關(guān)聯(lián)API,為什么要注意這個(gè)等下就會(huì)看到。

NSArray *arr = @[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"];
dispatch_apply(arr.count, dispatch_get_main_queue(), ^(size_t index) {
        NSLog(@"---%@", arr[index]);
    });

在主線程中運(yùn)行這段代碼,會(huì)發(fā)現(xiàn)不會(huì)有任何輸出并且程序也會(huì)卡死,沒(méi)錯(cuò)死鎖了。dispatch_apply函數(shù)和dispatch_sync相同,都會(huì)等待任務(wù)執(zhí)行結(jié)束,也就意味著dispatch_apply也是一個(gè)同步方法。在死鎖的判斷中,可以把它理解為一個(gè)sync函數(shù),這樣就明白為什么這里會(huì)發(fā)生死鎖了(關(guān)于死鎖的問(wèn)題在GCD整理(一))。

另外如果dispatch_apply函數(shù)指定了串行隊(duì)列作為參數(shù),那么遍歷會(huì)按照順序執(zhí)行,如果是并發(fā)隊(duì)列,執(zhí)行順序則不受控制。因?yàn)榇嘘?duì)列只對(duì)應(yīng)一條線程,并發(fā)隊(duì)列會(huì)對(duì)應(yīng)多個(gè)線程。

代碼和效果如下:

NSArray *arr = @[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"];
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_apply(arr.count, queue, ^(size_t index) {
    NSLog(@"---%@", arr[index]);
});
dispatch_apply串行隊(duì)列.png
NSArray *arr = @[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"];
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(arr.count, queue, ^(size_t index) {
    NSLog(@"---%@", arr[index]);
});
dispatch_apply并行隊(duì)列.png

dispatch_barrier_async

提供這樣一種功能,它將dispatch_barrier_async函數(shù)之前加入隊(duì)列的任務(wù)和dispatch_barrier_async之后加入隊(duì)列的任務(wù)分割開。并且dispatch_barrier_async的block會(huì)等待前面的任務(wù)執(zhí)行完成后在執(zhí)行,同時(shí)block執(zhí)行完成之前即使是并發(fā)隊(duì)列后面的任務(wù)也不會(huì)執(zhí)行。

dispatch_barrier_async執(zhí)行邏輯.png

一個(gè)典型的例子是使用dispatch_barrier_async處理讀寫問(wèn)題,在讀寫數(shù)據(jù)庫(kù)表的操作中,如果單純的使用dispatch_async函數(shù)執(zhí)行寫操作并且并發(fā)隊(duì)列中存在多個(gè)寫任務(wù),那么寫入的數(shù)據(jù)很有可能是錯(cuò)誤的。我們只希望同一時(shí)間只有一個(gè)任務(wù)在操作表。

另外一個(gè)情況,當(dāng)任務(wù)1-2要執(zhí)行讀取,讀取后任務(wù)3要進(jìn)行寫入,寫入完成后任務(wù)4再次讀取拿到新寫入的數(shù)據(jù)。這樣的情況使用dispatch_barrier_async就會(huì)很方便。

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    NSLog(@"read1");
});
dispatch_async(queue, ^{
    NSLog(@"read2");
});
dispatch_barrier_async(queue, ^{
    NSLog(@"write");
});
dispatch_async(queue, ^{
    NSLog(@"read4");
});

Dispatch Semaphore

信號(hào)量的作用和dispatch_barrier_async有些類似,它可以更加細(xì)致的控制數(shù)據(jù)的線程安全性。

描述信號(hào)量的概念可以生動(dòng)的用廁所來(lái)比喻,初始值為1的信號(hào)量就像只有一個(gè)坑的廁所,初始值為2就是兩個(gè)坑的廁所。每當(dāng)進(jìn)去一個(gè)人就會(huì)把門鎖上,信號(hào)量就減1;出來(lái)的時(shí)候自然會(huì)把門打開,信號(hào)量就加1。如果廁所所有的坑都被占了,后面來(lái)的人就只能等待。

可以用下面的語(yǔ)法創(chuàng)建一個(gè)信號(hào)量:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

dispatch_semaphore_create接受一個(gè)長(zhǎng)整形參數(shù),這段代碼就好比創(chuàng)建了一個(gè)只有一個(gè)坑的廁所??梢杂孟旅娴恼Z(yǔ)句來(lái)描述一個(gè)人占據(jù)了一個(gè)坑位的情況。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
dispatch_semaphore_wait(semaphore, time);

dispatch_semaphore_wait接受兩個(gè)參數(shù),參數(shù)1:對(duì)應(yīng)的信號(hào)量。參數(shù)2:超時(shí)時(shí)間。這個(gè)函數(shù)有一個(gè)長(zhǎng)整形返回值,返回值為0表示信號(hào)量的值大于等于1,或者在指定的等待時(shí)間內(nèi),超時(shí)返回非0。

引用一段別人對(duì)該函數(shù)的描述。

如果信號(hào)量的值大于0,該函數(shù)所處線程就繼續(xù)執(zhí)行下面的語(yǔ)句,并且將信號(hào)量的值減1;如果信號(hào)量的值為0,那么這個(gè)函數(shù)就阻塞當(dāng)前線程等待timeout,如果等待的期間信號(hào)量的值被dispatch_semaphore_signal函數(shù)加1了,且該函數(shù)(即dispatch_semaphore_wait)所處線程獲得了信號(hào)量,那么就繼續(xù)向下執(zhí)行并將信號(hào)量減1。如果等待期間沒(méi)有獲取到信號(hào)量或者信號(hào)量的值一直為0,那么等到timeout時(shí),其所處線程自動(dòng)執(zhí)行其后語(yǔ)句。參考鏈接

有減就一定有加,dispatch_semaphore_signal就是對(duì)應(yīng)從廁所開門出來(lái)那段劇情的函數(shù)。這個(gè)函數(shù)會(huì)把傳入的信號(hào)量加1,它同樣有返回值,0代表當(dāng)前并沒(méi)有等待中的線程需要被喚醒;非0代表當(dāng)前有至少一個(gè)等待中的線程,并且成功喚醒了其中一個(gè)。

運(yùn)行一段代碼看下效果:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);

for (NSInteger i = 0; i < 3; i++) {
    dispatch_async(queue, ^{
        NSInteger wait = dispatch_semaphore_wait(semaphore, time);
        NSLog(@"wait:%ld-%@", wait, [NSThread currentThread]);
        int i = 1;
        while (i < 1000000000) {
            i++;
        }
        NSLog(@"finish");
        NSInteger singal = dispatch_semaphore_signal(semaphore);
        NSLog(@"singal:%ld-%@", singal, [NSThread currentThread]);
    });
}
dispatch_semaphore運(yùn)行結(jié)果.png

分析代碼的運(yùn)行邏輯:number=2的線程最先拿到了信號(hào)量,并將信號(hào)量減1,與此同時(shí)3、4線程也運(yùn)行到了這里并且開始等待。在等待過(guò)程中2線程將循環(huán)執(zhí)行完畢并調(diào)用dispatch_semaphore_signal函數(shù),dispatch_semaphore_signal發(fā)現(xiàn)還有兩個(gè)線程在等待中,有線程在等待就返回非0(這里為1);同時(shí)線程3開始拿到信號(hào)量并執(zhí)行循環(huán)。這時(shí)候指定的等待時(shí)間3秒已經(jīng)到了,線程4不能再等了,于是開始執(zhí)行循環(huán)。這就意味著線程3和4在某一段時(shí)間內(nèi)是同時(shí)執(zhí)行循環(huán)操作的。隨后各自執(zhí)行dispatch_semaphore_signal函數(shù),此時(shí)已經(jīng)沒(méi)有等待線程存在所以返回0。

再放一個(gè)例子:

NSMutableArray *array = [[NSMutableArray alloc] init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

for (NSInteger i = 0; i < 1000; i++) {
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        [array addObject:@(i)];
        dispatch_semaphore_signal(semaphore);
    });
}

因?yàn)镹SMutableArray是非線程安全的,如果同一時(shí)間有多個(gè)線程對(duì)array數(shù)組進(jìn)行addObject操作一定會(huì)發(fā)生異常??梢允褂眯盘?hào)量來(lái)控制同一時(shí)間只有一條線程執(zhí)行addObject操作。

Dispatch Group

Dispatch Group 可以處理這樣一種情況:一個(gè)或多個(gè)并發(fā)隊(duì)列中有很多任務(wù)在執(zhí)行,而有一個(gè)任務(wù)必須在所有任務(wù)都執(zhí)行完成后最后執(zhí)行。比如要上傳多張圖片到后臺(tái),在全部上傳完成后要告知用戶圖片上傳完成。

這時(shí)Dispatch Group就會(huì)發(fā)揮極大的作用:

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_async(group, queue1, ^{
    NSLog(@"queue1 hello:%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue1, ^{
    NSLog(@"queue1 world:%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue2, ^{
    NSLog(@"queue2 hello:%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue2, ^{
    NSLog(@"queue2 world:%@", [NSThread currentThread]);
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"finish:%@", [NSThread currentThread]);
});

dispatch_group_create函數(shù)用來(lái)創(chuàng)建一個(gè) Dispatch Group。dispatch_group_async用法與dispatch_async一樣,只是多了個(gè)group參數(shù)。dispatch_group_notify函數(shù)會(huì)在group里的block全部執(zhí)行完成后運(yùn)行自己的block。

Dispatch Group還有一個(gè)很重要的方法:

dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout); 指定Dispatch Group的等待時(shí)間。參數(shù)1:組。參數(shù)2:超時(shí)時(shí)間。函數(shù)有一個(gè)返回值,為0表示在指定時(shí)間內(nèi)所有的block都已經(jīng)執(zhí)行完畢。非0表示在指定時(shí)間內(nèi)任務(wù)沒(méi)有執(zhí)行完。

還有比較常見的Dispatch Once,用法比較簡(jiǎn)單也不整理了。

最后,這篇GCD整理是閱讀《Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理》GCD部分的內(nèi)容后,結(jié)合其他大大們的博文以及自己的理解整理的一份筆記。水平有限,歡迎指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 背景 擔(dān)心了兩周的我終于輪到去醫(yī)院做胃鏡檢查了!去的時(shí)候我都想好了最壞的可能(胃癌),之前在網(wǎng)上查的癥狀都很相似。...
    Dely閱讀 9,400評(píng)論 21 42
  • 一、多線程簡(jiǎn)介: 所謂多線程是指一個(gè) 進(jìn)程 -- process(可以理解為系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序)中可以開...
    尋形覓影閱讀 1,183評(píng)論 0 6
  • “他~杜拉拉升職記深深的吸引了我,默默關(guān)注,緣分使然,與你相見。你~深邃的眼神,富有磁性的話語(yǔ),德藝雙馨的人格魅力...
    我是行走閱讀 397評(píng)論 0 0
  • 這樣每天都有點(diǎn)進(jìn)展的節(jié)奏,感覺(jué)很穩(wěn),很有鼓勵(lì)的方式在支持我向前走,雖然還有創(chuàng)業(yè)的孤獨(dú),但是已經(jīng)不重要了! 1.大學(xué)...
    一百八十斤大胖子閱讀 280評(píng)論 0 1
  • 感覺(jué) 該做些什么 嗯 或是說(shuō) 創(chuàng)造些什么 今天和某人看火星情報(bào)局 宇哥說(shuō)人越是成熟 越不會(huì)輕易承諾 除非能保證兌現(xiàn)...
    從沒(méi)想到會(huì)戀上白水啊閱讀 235評(píng)論 0 0

友情鏈接更多精彩內(nèi)容