iOS多線程GCD簡介(二)

在上一篇中,我們主要講了Dispatch Queue相關(guān)的內(nèi)容。這篇主要講一下一些和實際相關(guān)的使用實例,Dispatch Groups和Dispatch Semaphore。

dispatch_after

在我們開發(fā)過程中經(jīng)常會用到在多少秒后執(zhí)行某個方法,通常我們會用這個- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay函數(shù)。不過現(xiàn)在我們可以使用一個新的方法。

dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
    dispatch_after(delayTime, dispatch_get_main_queue(), ^{
        //do your task
    });

這樣我們就定義了一個延遲2秒后執(zhí)行的任務(wù)。不過在這里有一點需要說明的是,無論你用的是- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay還是dispatch_after這個方法。并不是說在你指定的延遲后立即運行,這些方法都是基于單線程的,它只是將你延遲的操作加入到隊列里面去。由于隊列里面都是FIFO,所以必須在你這個任務(wù)之前的操作完成后才會執(zhí)行你的方法。這個延遲只是大概的延遲。如果你在主線程里面調(diào)用這個方法,如果你主線程現(xiàn)在正在處理一個非常耗時的任務(wù),那么你這個延遲可能就會偏差很大。這個時候你可以再開個線程,在里面執(zhí)行你的延遲操作。

//放到全局默認的線程里面,這樣就不必等待當前調(diào)用線程執(zhí)行完后再執(zhí)行你的方法
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
    dispatch_after(delayTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //do your task
    });

dispatch_once

這個想必大家都非常的熟悉,這個在單例初始化的時候是蘋果官方推薦的方法。這個函數(shù)可以保證在應(yīng)用程序中只執(zhí)行指定的任務(wù)一次。即使在多線程的環(huán)境下執(zhí)行,也可以保證百分之百的安全。

    static id instance;
    static dispatch_once_t predicate;

    dispatch_once(&predicate, ^{
        //your init
    });

    return instance;
}

這里面的predicate必須是全局或者靜態(tài)對象。在多線程下同時訪問時,這個方法將被線程同步等待,直到指定的block執(zhí)行完成。

dispatch_apply

這個方法是執(zhí)行循環(huán)次數(shù)固定的迭代,如果在并發(fā)的queue里面可以提高性能。比如一個固定次數(shù)的for循環(huán)

for (int i = 0; i < 1000; i ++) {
        NSLog(@"---%d---", i);
    }

如果只是在一個線程里面或者在一個串行的隊列中是一樣的,一個個執(zhí)行。
現(xiàn)在我們用dispatch_apply來寫這個循環(huán):

dispatch_apply([array count], defaultQueue, ^(size_t i) {
        NSLog(@"----%@---", array[i]);
    });
    NSLog(@"end");

這個方法執(zhí)行后,它將像這個并發(fā)隊列中不斷的提交執(zhí)行的block。這個i是從0開始的,最后一個是[array count] - 1

使用這個方法有幾個注意點:

  1. 這個方法調(diào)用的時候會阻塞當前的線程,也就是上面的循環(huán)全部執(zhí)行完畢后,才會輸出end
  2. 在你使用這個任務(wù)進行操作的時候,你應(yīng)該確保你要執(zhí)行的各個任務(wù)是獨立的,而且執(zhí)行順序也是無關(guān)緊要的。
  3. 在你使用這個方法的時候,你還是要權(quán)衡下整體的性能的,如果你執(zhí)行的任務(wù)時間比線程切換的時間還短。那就得不償失了。

dispatch_group

在實際開發(fā)中,我們可能需要在一組操作全部完成后,才做其他操作。比如上傳一組圖片,或者下載多個文件。希望在全部完成時給用戶一個提示。如果這些操作在串行化的隊列中執(zhí)行的話,那么你可以很明確的知道,當最后一個任務(wù)執(zhí)行完成后,就全部完成了。這樣的操作也并木有發(fā)揮多線程的優(yōu)勢。我們可以在并發(fā)的隊列中進行這些操作,但是這個時候我們就不知道哪個是最后一個完成的了。這個時候我們可以借助dispatch_group:

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, defaultQueue, ^{
        //task1
        NSLog(@"1");
    });
    dispatch_group_async(group, defaultQueue, ^{
        //task2
        NSLog(@"2");
    });
    dispatch_group_async(group, defaultQueue, ^{
        //task3
        NSLog(@"3");
    });
    dispatch_group_async(group, defaultQueue, ^{
        //task4
        NSLog(@"4");
    });
    dispatch_group_async(group, defaultQueue, ^{
        //task5
        NSLog(@"5");
    });

    dispatch_group_notify(group, queue, ^{
        NSLog(@"finish");
    });

我們首先創(chuàng)建一個group然后往里面加入我們要執(zhí)行的操作,在dispatch_group_notify這個函數(shù)里面添加全部完成的操作。上面代碼執(zhí)行的時候,輸出的1,2,3,4,5的順序是不一定的,但是輸出的finish一定是在1,2,3,4,5之后。
對于添加到group的操作還有另外一個方法:

    dispatch_group_enter(group);
    dispatch_group_enter(group);

    dispatch_async(defaultQueue, ^{
        NSLog(@"1");
        dispatch_group_leave(group);
    });

    dispatch_async(defaultQueue, ^{
        NSLog(@"2");
        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, queue, ^{
        NSLog(@"finish");
    });

我們可以用dispatch_group_enter來表示添加任務(wù),dispatch_group_leave來表示有個任務(wù)已經(jīng)完成了。用這個方法一定要注意必須成雙成對。

線程同步

在多線程中一個比較重要的東西就是線程同步的問題。如果多個線程只是對某個資源只是讀的過程,那么就不存在這個問題了。如果某個線程對這個資源需要進行寫的操作,那這個時候就會出現(xiàn)數(shù)據(jù)不一致的問題了。

使用dispatch_barrier_async

    __block NSString *strTest = @"test";

    dispatch_async(defaultQueue, ^{
        if ([strTest isEqualToString:@"test"]) {
            NSLog(@"--%@--1-", strTest);
            [NSThread sleepForTimeInterval:1];
            if ([strTest isEqualToString:@"test"]) {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"--%@--2-", strTest);
            } else {
                NSLog(@"====changed===");
            }
        }
    });
    dispatch_async(defaultQueue, ^{
        NSLog(@"--%@--3-", strTest);
    });
    dispatch_async(defaultQueue, ^{
        strTest = @"modify";
        NSLog(@"--%@--4-", strTest);
    });

看看這個模擬的場景,我們讓各個線程去訪問這個變量,其中有個操作是要修改這個變量。我們把第一個操作先判斷有木有改變,然后故意延遲一下,這個時候我們看下輸出結(jié)果:

2015-01-03 15:42:21.351 測試[1652:60015] --test--3-
2015-01-03 15:42:21.351 測試[1652:60013] --modify--4-
2015-01-03 15:42:21.351 測試[1652:60014] --test--1-
2015-01-03 15:42:22.355 測試[1652:60014] ====changed===

我們可以看到,再次判斷的時候,已經(jīng)被修改了,如果我們在實際的業(yè)務(wù)中這樣去判斷某些關(guān)鍵性的變量,可能就會出現(xiàn)嚴重的問題。下面看看我們?nèi)绾问褂?code>dispatch_barrier_async來進行同步:

 //并發(fā)隊列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);

    __block NSString *strTest = @"test";

    dispatch_async(concurrentQueue, ^{
        if ([strTest isEqualToString:@"test"]) {
            NSLog(@"--%@--1-", strTest);
            [NSThread sleepForTimeInterval:1];
            if ([strTest isEqualToString:@"test"]) {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"--%@--2-", strTest);
            } else {
                NSLog(@"====changed===");
            }
        }
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"--%@--3-", strTest);
    });
    dispatch_barrier_async(concurrentQueue, ^{
        strTest = @"modify";
        NSLog(@"--%@--4-", strTest);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"--%@--5-", strTest);
    });

現(xiàn)在看下輸出結(jié)果:

2015-01-03 16:00:27.552 測試[1786:65947] --test--1-
2015-01-03 16:00:27.552 測試[1786:65965] --test--3-
2015-01-03 16:00:29.553 測試[1786:65947] --test--2-
2015-01-03 16:00:29.553 測試[1786:65947] --modify--4-
2015-01-03 16:00:29.553 測試[1786:65947] --modify--5-

現(xiàn)在我們可以發(fā)現(xiàn)操作4用dispatch_barrier_async加入操作后,前面的操作3之前都操作完成之前這個strTest都沒有變。而后面的操作都是改變后的值。這樣我們的數(shù)據(jù)沖突的問題就解決了。
現(xiàn)在說明下這個函數(shù)干的事情,當這個函數(shù)加入到隊列后,里面block并不是立即執(zhí)行的,它會先等待之前正在執(zhí)行的block全部完成后,才執(zhí)行,并且在它之后加入到隊列中的block也在它操作結(jié)束后才能恢復(fù)之前的并發(fā)執(zhí)行。我們可以把這個函數(shù)理解為一條分割線,之前的操作,之后加入的操作。還有一個點要說明的是這個queue必須是用dispatch_queue_create創(chuàng)建出來的才行。

使用Dispatch Semaphore

dispatch_semaphore_t 類似信號量,可以用來控制訪問某一資源訪問數(shù)量。
使用過程:

  1. 先創(chuàng)建一個Dispatch Semaphore對象,用整數(shù)值表示資源的可用數(shù)量
  2. 在每個任務(wù)中,調(diào)用dispatch_semaphore_wait來等待
  3. 獲得資源就可以進行操作
  4. 操作完后調(diào)用dispatch_semaphore_signal來釋放資源
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    __block NSString *strTest = @"test";

    dispatch_async(concurrentQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        if ([strTest isEqualToString:@"test"]) {
            NSLog(@"--%@--1-", strTest);
            [NSThread sleepForTimeInterval:1];
            if ([strTest isEqualToString:@"test"]) {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"--%@--2-", strTest);
            } else {
                NSLog(@"====changed===");
            }
        }
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(concurrentQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"--%@--3-", strTest);
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(concurrentQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        strTest = @"modify";
        NSLog(@"--%@--4-", strTest);
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(concurrentQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"--%@--5-", strTest);
        dispatch_semaphore_signal(semaphore);
    });

這樣我們一樣可以保證,線程的數(shù)據(jù)安全。

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

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

  • 一、多線程簡介: 所謂多線程是指一個 進程 -- process(可以理解為系統(tǒng)中正在運行的一個應(yīng)用程序)中可以開...
    尋形覓影閱讀 1,189評論 0 6
  • 背景 擔(dān)心了兩周的我終于輪到去醫(yī)院做胃鏡檢查了!去的時候我都想好了最壞的可能(胃癌),之前在網(wǎng)上查的癥狀都很相似。...
    Dely閱讀 9,408評論 21 42
  • 這是最近在讀的書。法頂禪師著,活在時間之外。這本書一直被我放在床頭,失眠的夜,或是有閒暇的下午,躺在床上,隨手拿起...
    此刻是金__閱讀 255評論 3 0
  • 片片黃葉迎秋來,只覺炎夏未消亡。百轉(zhuǎn)千愁至,莫言人心死,悠樂非我忘,紅塵萬千絲難斷…… 己所不欲勿施人,花退殘...
    知味如水閱讀 233評論 0 0
  • 回答網(wǎng)友的一個問題,男女交往中男性的核心競爭力是什么? 在不同階段,男性的核心競爭力有不同的定義。如果是在大學(xué)期間...
    人生葵花寶典閱讀 1,822評論 4 14

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