iOS多線程GCD簡介(二)

在上一篇中,咱們主要講了Dispatch Queue相關的內容。這篇主要講一下一些和實際相關的使用實例,Dispatch Groups和Dispatch Semaphore。安全

dispatch_after

在咱們開發過程當中常常會用到在多少秒後執行某個方法,一般咱們會用這個- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay函數。不過如今咱們可使用一個新的方法。多線程

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

這樣咱們就定義了一個延遲2秒後執行的任務。不過在這裏有一點須要說明的是,不管你用的是- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay仍是dispatch_after這個方法。並非說在你指定的延遲後當即運行,這些方法都是基於單線程的,它只是將你延遲的操做加入到隊列裏面去。因爲隊列裏面都是FIFO,因此必須在你這個任務以前的操做完成後纔會執行你的方法。這個延遲只是大概的延遲。若是你在主線程裏面調用這個方法,若是你主線程如今正在處理一個很是耗時的任務,那麼你這個延遲可能就會誤差很大。這個時候你能夠再開個線程,在裏面執行你的延遲操做。併發

//放到全局默認的線程裏面,這樣就沒必要等待當前調用線程執行完後再執行你的方法
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

這個想必你們都很是的熟悉,這個在單例初始化的時候是蘋果官方推薦的方法。這個函數能夠保證在應用程序中只執行指定的任務一次。即便在多線程的環境下執行,也能夠保證百分之百的安全。app

static id instance;
    static dispatch_once_t predicate;

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

    return instance;
}

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

dispatch_apply

這個方法是執行循環次數固定的迭代,若是在併發的queue裏面能夠提升性能。好比一個固定次數的for循環函數

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

若是隻是在一個線程裏面或者在一個串行的隊列中是同樣的,一個個執行。
如今咱們用dispatch_apply來寫這個循環:性能

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

這個方法執行後,它將像這個併發隊列中不斷的提交執行的block。這個i是從0開始的,最後一個是[array count] - 1測試

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

  1. 這個方法調用的時候會阻塞當前的線程,也就是上面的循環所有執行完畢後,纔會輸出end
  2. 在你使用這個任務進行操做的時候,你應該確保你要執行的各個任務是獨立的,並且執行順序也是可有可無的。
  3. 在你使用這個方法的時候,你仍是要權衡下總體的性能的,若是你執行的任務時間比線程切換的時間還短。那就得不償失了。

dispatch_group

在實際開發中,咱們可能須要在一組操做所有完成後,才作其餘操做。好比上傳一組圖片,或者下載多個文件。但願在所有完成時給用戶一個提示。若是這些操做在串行化的隊列中執行的話,那麼你能夠很明確的知道,當最後一個任務執行完成後,就所有完成了。這樣的操做也並木有發揮多線程的優點。咱們能夠在併發的隊列中進行這些操做,可是這個時候咱們就不知道哪一個是最後一個完成的了。這個時候咱們能夠藉助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");
    });

咱們首先建立一個group而後往裏面加入咱們要執行的操做,在dispatch_group_notify這個函數裏面添加所有完成的操做。上面代碼執行的時候,輸出的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來表示添加任務,dispatch_group_leave來表示有個任務已經完成了。用這個方法必定要注意必須成雙成對。

線程同步

在多線程中一個比較重要的東西就是線程同步的問題。若是多個線程只是對某個資源只是讀的過程,那麼就不存在這個問題了。若是某個線程對這個資源須要進行寫的操做,那這個時候就會出現數據不一致的問題了。

使用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);
    });

看看這個模擬的場景,咱們讓各個線程去訪問這個變量,其中有個操做是要修改這個變量。咱們把第一個操做先判斷有木有改變,而後故意延遲一下,這個時候咱們看下輸出結果:

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===

咱們能夠看到,再次判斷的時候,已經被修改了,若是咱們在實際的業務中這樣去判斷某些關鍵性的變量,可能就會出現嚴重的問題。下面看看咱們如何使用dispatch_barrier_async來進行同步:

//併發隊列
    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);
    });

如今看下輸出結果:

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-

如今咱們能夠發現操做4用dispatch_barrier_async加入操做後,前面的操做3以前都操做完成以前這個strTest都沒有變。然後面的操做都是改變後的值。這樣咱們的數據衝突的問題就解決了。
如今說明下這個函數乾的事情,當這個函數加入到隊列後,裏面block並非當即執行的,它會先等待以前正在執行的block所有完成後,才執行,而且在它以後加入到隊列中的block也在它操做結束後才能恢復以前的併發執行。咱們能夠把這個函數理解爲一條分割線,以前的操做,以後加入的操做。還有一個點要說明的是這個queue必須是用dispatch_queue_create建立出來的才行。

使用Dispatch Semaphore

dispatch_semaphore_t 相似信號量,能夠用來控制訪問某一資源訪問數量。
使用過程:

  1. 先建立一個Dispatch Semaphore對象,用整數值表示資源的可用數量
  2. 在每一個任務中,調用dispatch_semaphore_wait來等待
  3. 得到資源就能夠進行操做
  4. 操做完後調用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);
    });

這樣咱們同樣能夠保證,線程的數據安全。

相關文章
相關標籤/搜索