多線程 通過GCD實現網絡並行

多個接口請求數據,需要監聽接口請求完成,才能進行下一步的操作,我們採用網絡請求是異步的。那麼我們要如何監聽呢?

通常情況下,多個網絡請求同時執行,等所有網絡請求返回後,再進行下一步操作,刷新UI,我們會想到 dispatch_group_asyncdispatch_group_notify 結合使用。

一、某界面存在多個請求,希望所有請求均結束才進行某操作。

對於這一問題的解決方案很容易想到通過線程組進行實現。代碼如下:

示例一

- (void)example1 {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"Request_1,%@", [NSThread currentThread]);
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"Request_2,%@", [NSThread currentThread]);
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"Request_3,%@", [NSThread currentThread]);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"返回主線程,刷新UI,%@", [NSThread currentThread]);
    });
}

結果如下

Request_2
Request_1
Request_3
任務均完成,刷新界面

根據打印結果觀察看似並沒有什麼問題,但需要注意的是Request_1、Request_2等在真實開發中通常對應爲某個網絡請求。而網絡請求通常爲異步,耗時的,下面我們用 dispatch_after 延遲模擬網絡請求

示例二

- (void)example2 {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
             NSLog(@"Request_1,%@", [NSThread currentThread]);
        });
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"Request_2,%@", [NSThread currentThread]);
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"Request_3,%@", [NSThread currentThread]);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"返回主線程,刷新UI,%@", [NSThread currentThread]);
    });
}

結果如下

Request_2
Request_3
任務均完成,刷新界面
Request_1

可以看到任務一的延遲請求完成造成了一些問題,這時候我們可以用信號量即dispatch_semaphore,包括三個部分:dispatch_semaphore_create()dispatch_semaphore_signal()dispatch_semaphore_wait(),三個結合使用

dispatch_semaphore_create   創建一個semaphore
dispatch_semaphore_signal   發送一個信號
dispatch_semaphore_wait    等待信號

簡單的介紹一下這三個函數,第一個函數有一個整形的參數,我們可以理解爲信號的總量,dispatch_semaphore_signal是發送一個信號,自然會讓信號總量加1,dispatch_semaphore_wait等待信號,當信號總量少於0的時候就會一直等待,否則就可以正常的執行,並讓信號總量-1,根據這樣的原理,我們便可以快速的創建一個併發控制來同步任務和有限資源訪問控制。

這裏有一篇關於dispatch_semaphore的使用的文章,大家可以去詳細瞭解下

我這裏摘抄了一些,方便以後複習查閱[dispatch_semaphore](#dispatch_semaphore)

示例三

///通過semaphore 多線程並行
- (void)example3 {

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_group_t group = dispatch_group_create();

    ///初使值爲0的semaphore
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);


    dispatch_group_async(group, queue, ^{

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),

            dispatch_get_main_queue(), ^{
                ///可以理解爲:信號量的值加一
                dispatch_semaphore_signal(sem);
                NSLog(@"Request_1,%@", [NSThread currentThread]);
        });
        ///可以理解爲:信號量的值減一
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    });

    dispatch_group_async(group, queue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            dispatch_semaphore_signal(sem);
            NSLog(@"Request_2,%@", [NSThread currentThread]);
        });
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"Request_3,%@", [NSThread currentThread]);
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"請求完成,%@", [NSThread currentThread]);
    });
}

結果如下

Request_2
Request_3
Request_1
任務均完成,刷新界面

相關

  停車場剩餘4個車位,那麼即使同時來了四輛車也能停的下。如果此時來了五輛車,那麼就有一輛需要等待。

  信號量的值就相當於剩餘車位的數目,dispatch_semaphore_wait函數就相當於來了一輛車,dispatch_semaphore_signal

  就相當於走了一輛車。停車位的剩餘數目在初始化的時候就已經指明瞭(dispatch_semaphore_create(long value)),

  調用一次dispatch_semaphore_signal,剩餘的車位就增加一個;調用一次dispatch_semaphore_wait剩餘車位就減少一個;

  當剩餘車位爲0時,再來車(即調用dispatch_semaphore_wait)就只能等待。有可能同時有幾輛車等待一個停車位。有些車主

  沒有耐心,給自己設定了一段等待時間,這段時間內等不到停車位就走了,如果等到了就開進去停車。而有些車主就像把車停在這,

  所以就一直等下去。

這篇文章的示例大家也可以看一看

這樣實現了我們的需求,我們還可以使用 dispatch_group_enterdispatch_group_leave 結合實現,使用 dispatch_group_enter() 函數進入到任務組中,然後異步執行隊列中的任務,最後使用 dispatch_group_leave() 函數離開任務組即可,

示例四

///通過enter和leave 多線程並行
- (void)example4 {
    dispatch_group_t group = dispatch_group_create();

    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);

    dispatch_group_enter(group);

    dispatch_group_async(group, queue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
                dispatch_get_main_queue(), ^{

            NSLog(@"Request_1,%@", [NSThread currentThread]);

            dispatch_group_leave(group);
        });
    });


    dispatch_group_enter(group);

    dispatch_group_async(group, queue, ^{

        NSLog(@"Request_2,%@", [NSThread currentThread]);

        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);

    dispatch_group_async(group, queue, ^{

        NSLog(@"Request_3,%@", [NSThread currentThread]);

        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, queue, ^{
        NSLog(@"完成,%@", [NSThread currentThread]);
    });

}

結果如下

2017-05-18 11:02:24.233  Request_2,<NSThread: 0x60800027ab80>{number = 3, name = (null)}
2017-05-18 11:02:24.233  Request_3,<NSThread: 0x60800027ab80>{number = 3, name = (null)}
2017-05-18 11:02:25.329  Request_1,<NSThread: 0x600000261ac0>{number = 1, name = main}
2017-05-18 11:02:25.330  完成,<NSThread: 0x60800026eb40>{number = 4, name = (null)}

示例五

///FIFO
- (void)example5 {

    dispatch_semaphore_t sem = dispatch_semaphore_create(1);

    dispatch_queue_t queue = dispatch_queue_create("testBlock", NULL);

    dispatch_async(queue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"1");
            dispatch_semaphore_signal(sem);
        });

    });
    dispatch_async(queue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"2");
            dispatch_semaphore_signal(sem);
        });

    });

    dispatch_async(queue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"3");
            dispatch_semaphore_signal(sem);
        });
    });

    dispatch_async(queue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"4");
            dispatch_semaphore_signal(sem);
        });
    });

}   

結果如下

2017-05-18 10:49:57.246  [1441:96225] 1
2017-05-18 10:49:57.247  [1441:96225] 2
2017-05-18 10:49:57.247  [1441:96225] 3
2017-05-18 10:49:57.248  [1441:96225] 4

dispatch_semaphore

 (1)dispatch_semaphore_create的聲明爲:

  dispatch_semaphore_t  dispatch_semaphore_create(long value);

  傳入的參數爲long,輸出一個dispatch_semaphore_t類型且值爲value的信號量。

  值得注意的是,這裏的傳入的參數value必須大於或等於0,否則dispatch_semaphore_create會返回NULL。

  (3) dispatch_semaphore_wait的聲明爲:

  long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

  這個函數會使傳入的信號量dsema的值減1;

  這個函數的作用是這樣的,如果dsema信號量的值大於0,該函數所處線程就繼續執行下面的語句,並且將信號量的值減1;

  如果desema的值爲0,那麼這個函數就阻塞當前線程等待timeout(注意timeout的類型爲dispatch_time_t,

  不能直接傳入整形或float型數),如果等待的期間desema的值被dispatch_semaphore_signal函數加1了,

  且該函數(即dispatch_semaphore_wait)所處線程獲得了信號量,那麼就繼續向下執行並將信號量減1。

  如果等待期間沒有獲取到信號量或者信號量的值一直爲0,那麼等到timeout時,其所處線程自動執行其後語句。
  

(4)dispatch_semaphore_signal的返回值爲long類型,當返回值爲0時表示當前並沒有線程等待其處理的信號量,其處理

  的信號量的值加1即可。當返回值不爲0時,表示其當前有(一個或多個)線程等待其處理的信號量,並且該函數喚醒了一

  個等待的線程(當線程有優先級時,喚醒優先級最高的線程;否則隨機喚醒)。

  dispatch_semaphore_wait的返回值也爲long型。當其返回0時表示在timeout之前,該函數所處的線程被成功喚醒。

  當其返回不爲0時,表示timeout發生。

(5)在設置timeout時,比較有用的兩個宏:DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER。

  DISPATCH_TIME_NOW  表示當前;

  DISPATCH_TIME_FOREVER  表示遙遠的未來;

  一般可以直接設置timeout爲這兩個宏其中的一個,或者自己創建一個dispatch_time_t類型的變量。

  創建dispatch_time_t類型的變量有兩種方法,dispatch_time和dispatch_walltime。

  利用創建dispatch_time創建dispatch_time_t類型變量的時候一般也會用到這兩個變量。

  dispatch_time的聲明如下:

  dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);

  其參數when需傳入一個dispatch_time_t類型的變量,和一個delta值。表示when加delta時間就是timeout的時間。

  例如:dispatch_time_t  t = dispatch_time(DISPATCH_TIME_NOW, 1*1000*1000*1000);

     表示當前時間向後延時一秒爲timeout的時間。

相關

1、多個網絡請求併發執行、順序執行

2、把握AFNet網絡請求完成的正確時機

3、關於dispatch_semaphore的使用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章