iOS之利用GCD信號量控制并發(fā)網(wǎng)絡(luò)請求

對計算機了解的都會知道信號量的作用,當我們多個線程要訪問同一個資源的時候,往往會設(shè)置一個信號量,當信號量大于0的時候,新的線程可以去操作這個資源,操作時信號量-1,操作完后信號量+1,當信號量等于0的時候,必須等待,所以通過控制信號量,我們可以控制能夠同時進行的并發(fā)數(shù)。

在網(wǎng)絡(luò)請求的開發(fā)中,經(jīng)常會遇到兩種情況,一種是我在一個界面需要同時請求多種數(shù)據(jù),比如列表數(shù)據(jù)、廣告數(shù)據(jù)等,全部請求到后再一起刷新界面。另一種是我的請求必須滿足一定順序,比如必須先請求個人信息,然后根據(jù)個人信息請求相關(guān)內(nèi)容。這些要求對于普通的操作是可以做到并發(fā)控制和依賴操作的,但是對于網(wǎng)絡(luò)請求這種需要時間的請求來說,效果往往與預期的不一樣,這時候就需要用信號量來做一個控制。

GCD信號量

信號量是一個整數(shù),在創(chuàng)建的時候會有一個初始值,這個初始值往往代表我要控制的同時操作的并發(fā)數(shù)。在操作中,對信號量會有兩種操作:信號通知與等待。信號通知時,信號量會+1,等待時,如果信號量大于0,則會將信號量-1,否則,會等待直到信號量大于0。什么時候會大于零呢?往往是在之前某個操作結(jié)束后,我們發(fā)出信號通知,讓信號量+1。

說完概念,我們來看看GCD中的三個信號量操作:

  • dispatch_semaphore_create:創(chuàng)建一個信號量(semaphore)
  • dispatch_semaphore_signal:信號通知,即讓信號量+1
  • dispatch_semaphore_wait:等待,直到信號量大于0時,即可操作,同時將信號量-1

在使用的時候,往往會創(chuàng)建一個信號量,然后進行多個操作,每次操作都等待信號量大于0再操作,同時信號昂-1,操作完后將信號量+1,類似下面這個過程:

dispatch_semaphore_t sema = dispatch_semaphore_create(5);
for (100次循環(huán)操作) {
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 操作
        dispatch_semaphore_signal(sema);
    });
}

上面代碼表示我要操作100次,但是控制允許同時并發(fā)的操作最多只有5次,當并發(fā)量達到5后,信號量就減小到0了,這時候wait操作會起作用,DISPATCH_TIME_FOREVER表示會永遠等待,一直等到信號量大于0,也就是有操作完成了,將信號量+1了,這時候才可以結(jié)束等待,進行操作,并且將信號量-1,這樣新的任務(wù)又要等待。

多個請求結(jié)束后統(tǒng)一操作

假設(shè)我們一個頁面需要同時進行多個請求,他們之間倒是不要求順序關(guān)系,但是要求等他們都請求完畢了再進行界面刷新或者其他什么操作。

這個需求我們一般可以用GCD的group和notify來做到:

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //請求1
        NSLog(@"Request_1");
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //請求2
        NSLog(@"Request_2");
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //請求3
        NSLog(@"Request_3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //界面刷新
        NSLog(@"任務(wù)均完成,刷新界面");
    });

notify的作用就是在group中的其他操作全部完成后,再操作自己的內(nèi)容,所以我們會看到上面三個內(nèi)容都打印出來后,才打印界面刷新的內(nèi)容。

但是當將上面三個操作改成真實的網(wǎng)絡(luò)操作后,這個簡單的做法會變得無效,為什么呢?因為網(wǎng)絡(luò)請求需要時間,而線程的執(zhí)行并不會等待請求完成后才真正算作完成,而是只負責將請求發(fā)出去,線程就認為自己的任務(wù)算完成了,當三個請求都發(fā)送出去,就會執(zhí)行notify中的內(nèi)容,但請求結(jié)果返回的時間是不一定的,也就導致界面都刷新了,請求才返回,這就是無效的。

要解決這個問題,我們就要用到上面說的信號量來操作了。

在每個請求開始之前,我們創(chuàng)建一個信號量,初始為0,在請求操作之后,我們設(shè)一個dispatch_semaphore_wait,在請求到結(jié)果之后,再將信號量+1,也即是dispatch_semaphore_signal。這樣做的目的是保證在請求結(jié)果沒有返回之前,一直讓線程等待在那里,這樣一個線程的任務(wù)一直在等待,就不會算作完成,notify的內(nèi)容也就不會執(zhí)行了,直到每個請求的結(jié)果都返回了,線程任務(wù)才能夠結(jié)束,這時候notify也才能夠執(zhí)行。偽代碼如下:

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[網(wǎng)絡(luò)請求:{
        成功:dispatch_semaphore_signal(sema);
        失?。篸ispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

多個請求順序執(zhí)行

有時候我們需要按照順序執(zhí)行多次請求,比如先請求到用戶信息,然后根據(jù)用戶信息中的內(nèi)容去請求相關(guān)的數(shù)據(jù),這在平常的代碼中直接按照順序往下寫代碼就可以了,但這里因為涉及到多線程之間的關(guān)系,就叫做線程依賴。

線程依賴用GCD做比較麻煩,建議用NSOperationQueue做,可以更加方便的設(shè)置任務(wù)之間的依賴。

    // 1.任務(wù)一:獲取用戶信息
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [self request_A];
    }];
 
    // 2.任務(wù)二:請求相關(guān)數(shù)據(jù)
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [self request_B];
    }];
 
    // 3.設(shè)置依賴
    [operation2 addDependency:operation1];// 任務(wù)二依賴任務(wù)一
 
    // 4.創(chuàng)建隊列并加入任務(wù)
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperations:@[operation2, operation1] waitUntilFinished:NO];

一般的多線程操作這樣做是可以的,線程2會等待線程1完成后再執(zhí)行。但是對于網(wǎng)絡(luò)請求,問題又來了,同樣,網(wǎng)絡(luò)請求需要時間,線程發(fā)出請求后即認為任務(wù)完成了,并不會等待返回后的操作,這就失去了意義。

要解決這個問題,還是用信號量來控制,其實是一個道理,代碼也是一樣的,在一個任務(wù)操作中:

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[網(wǎng)絡(luò)請求:{
        成功:dispatch_semaphore_signal(sema);
        失?。篸ispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

還是去等待請求返回后,才讓任務(wù)結(jié)束。而依賴關(guān)系則通過NSOperationQueue來實現(xiàn)。

結(jié)

其實歸根結(jié)底,中心思想就是通過信號量,來控制線程任務(wù)什么時候算作結(jié)束,如果不用信號量,請求發(fā)出后即認為任務(wù)完成,而網(wǎng)絡(luò)請求又要不同時間,所以會打亂順序。因此用一個信號量來控制在單個線程操作內(nèi),必須等待請求返回,自己要執(zhí)行的操作完成后,才將信號量+1,這時候一直處于等待的代碼也得以執(zhí)行通過,任務(wù)才算作完成。

通過這個方法,就可以解決由于網(wǎng)絡(luò)請求耗時特性而帶來的一些意想不到的多線程處理的問題。

參考資料:
1、http://www.cocoachina.com/ios/20170428/19150.html
2、http://blog.csdn.net/fhbystudy/article/details/25918451


查看作者首頁

?著作權(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)容

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