1. 程式人生 > >iOS之利用GCD訊號量控制併發網路請求

iOS之利用GCD訊號量控制併發網路請求

對計算機瞭解的都會知道訊號量的作用,當我們多個執行緒要訪問同一個資源的時候,往往會設定一個訊號量,當訊號量大於0的時候,新的執行緒可以去操作這個資源,操作時訊號量-1,操作完後訊號量+1,當訊號量等於0的時候,必須等待,所以通過控制訊號量,我們可以控制能夠同時進行的併發數。

在網路請求的開發中,經常會遇到兩種情況,一種是我在一個介面需要同時請求多種資料,比如列表資料、廣告資料等,全部請求到後再一起重新整理介面。另一種是我的請求必須滿足一定順序,比如必須先請求個人資訊,然後根據個人資訊請求相關內容。這些要求對於普通的操作是可以做到併發控制和依賴操作的,但是對於網路請求這種需要時間的請求來說,效果往往與預期的不一樣,這時候就需要用訊號量來做一個控制。

GCD訊號量

訊號量是一個整數,在建立的時候會有一個初始值,這個初始值往往代表我要控制的同時操作的併發數。在操作中,對訊號量會有兩種操作:訊號通知與等待。訊號通知時,訊號量會+1,等待時,如果訊號量大於0,則會將訊號量-1,否則,會等待直到訊號量大於0。什麼時候會大於零呢?往往是在之前某個操作結束後,我們發出訊號通知,讓訊號量+1。

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

  • dispatch_semaphore_create:建立一個訊號量(semaphore)
  • dispatch_semaphore_signal:訊號通知,即讓訊號量+1
  • dispatch_semaphore_wait:等待,直到訊號量大於0時,即可操作,同時將訊號量-1

在使用的時候,往往會建立一個訊號量,然後進行多個操作,每次操作都等待訊號量大於0再操作,同時訊號昂-1,操作完後將訊號量+1,類似下面這個過程:

dispatch_semaphore_t sema = dispatch_semaphore_create(5);
for (100次迴圈操作) {
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 操作
        dispatch_semaphore_signal(sema);
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

上面程式碼表示我要操作100次,但是控制允許同時併發的操作最多隻有5次,當併發量達到5後,訊號量就減小到0了,這時候wait操作會起作用,DISPATCH_TIME_FOREVER表示會永遠等待,一直等到訊號量大於0,也就是有操作完成了,將訊號量+1了,這時候才可以結束等待,進行操作,並且將訊號量-1,這樣新的任務又要等待。

多個請求結束後統一操作

假設我們一個頁面需要同時進行多個請求,他們之間倒是不要求順序關係,但是要求等他們都請求完畢了再進行介面重新整理或者其他什麼操作。

這個需求我們一般可以用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(@"任務均完成,重新整理介面");
    });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

notify的作用就是在group中的其他操作全部完成後,再操作自己的內容,所以我們會看到上面三個內容都打印出來後,才打印介面重新整理的內容。

但是當將上面三個操作改成真實的網路操作後,這個簡單的做法會變得無效,為什麼呢?因為網路請求需要時間,而執行緒的執行並不會等待請求完成後才真正算作完成,而是隻負責將請求發出去,執行緒就認為自己的任務算完成了,當三個請求都發送出去,就會執行notify中的內容,但請求結果返回的時間是不一定的,也就導致介面都重新整理了,請求才返回,這就是無效的。

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

在每個請求開始之前,我們建立一個訊號量,初始為0,在請求操作之後,我們設一個dispatch_semaphore_wait,在請求到結果之後,再將訊號量+1,也即是dispatch_semaphore_signal。這樣做的目的是保證在請求結果沒有返回之前,一直讓執行緒等待在那裡,這樣一個執行緒的任務一直在等待,就不會算作完成,notify的內容也就不會執行了,直到每個請求的結果都返回了,執行緒任務才能夠結束,這時候notify也才能夠執行。虛擬碼如下:

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[網路請求:{
        成功:dispatch_semaphore_signal(sema);
        失敗:dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

多個請求順序執行

有時候我們需要按照順序執行多次請求,比如先請求到使用者資訊,然後根據使用者資訊中的內容去請求相關的資料,這在平常的程式碼中直接按照順序往下寫程式碼就可以了,但這裡因為涉及到多執行緒之間的關係,就叫做執行緒依賴。

執行緒依賴用GCD做比較麻煩,建議用NSOperationQueue做,可以更加方便的設定任務之間的依賴。

    // 1.任務一:獲取使用者資訊
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [self request_A];
    }];

    // 2.任務二:請求相關資料
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [self request_B];
    }];

    // 3.設定依賴
    [operation2 addDependency:operation1];// 任務二依賴任務一

    // 4.建立佇列並加入任務
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperations:@[operation2, operation1] waitUntilFinished:NO];
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

一般的多執行緒操作這樣做是可以的,執行緒2會等待執行緒1完成後再執行。但是對於網路請求,問題又來了,同樣,網路請求需要時間,執行緒發出請求後即認為任務完成了,並不會等待返回後的操作,這就失去了意義。

要解決這個問題,還是用訊號量來控制,其實是一個道理,程式碼也是一樣的,在一個任務操作中:

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[網路請求:{
        成功:dispatch_semaphore_signal(sema);
        失敗:dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

還是去等待請求返回後,才讓任務結束。而依賴關係則通過NSOperationQueue來實現。

其實歸根結底,中心思想就是通過訊號量,來控制執行緒任務什麼時候算作結束,如果不用訊號量,請求發出後即認為任務完成,而網路請求又要不同時間,所以會打亂順序。因此用一個訊號量來控制在單個執行緒操作內,必須等待請求返回,自己要執行的操作完成後,才將訊號量+1,這時候一直處於等待的程式碼也得以執行通過,任務才算作完成。

通過這個方法,就可以解決由於網路請求耗時特性而帶來的一些意想不到的多執行緒處理的問題。

相關推薦

iOS利用GCD訊號控制併發網路請求

引對計算機瞭解的都會知道訊號量的作用,當我們多個執行緒要訪問同一個資源的時候,往往會設定一個訊號量,當訊號量大於0的時候,新的執行緒可以去操作這個資源,操作時訊號量-1,操作完後訊號量+1,當訊號量等於0的時候,必須等待,所以通過控制訊號量,我們可以控制能夠同時進行的併發數。

GCD 訊號控制併發 (dispatch_semaphore)

當我們在處理一系列執行緒的時候,當數量達到一定量,在以前我們可能會選擇使用NSOperationQueue來處理併發控制,但如何在GCD中快速的控制併發呢?答案就是dispatch_semaphore,對經常做unix開發的人來講,我所介紹的內容可能就顯得非常入門級了,訊號

GCD訊號控制併發

例程一:控制執行緒數量 //訊號量控制併發 dispatch_group_t group = dispatch_group_create(); dispatch_semaphore_t semaphore = dispatch_semaphore_c

Java併發閉鎖/柵欄/訊號併發模型和鎖

threadLocal能夠為每一個執行緒維護變數副本,常用於在多執行緒中用空間換時間     程序死鎖:程序死鎖,指多個程序迴圈等待他方佔有的資源而一直等待下去的局面;  程序活鎖:執行緒1,2需要同時佔有a,b才可以,1佔有a,2佔有b,為了避免死鎖,

多執行緒 Semaphore 訊號控制

Semaphore(訊號量) 用來控制同時訪問特定資源的執行緒數量。可以起到限流的作用。它與之前寫到的Guava API 中的令牌桶 RateLimiter的區別在於,令牌桶是控制了最終能進入訪問資源的恆定流量。會拋棄掉一些過剩流量的進入。而Semaphore 保證的是進入流量的恆定速率,這些流量最

Java併發閉鎖/柵欄/訊號

一、Java多執行緒總結: 描述執行緒的類:Runable和Thread都屬於java.lang包。 內建鎖synchronized屬於jvm關鍵字,內建條件佇列操作介面Object.wait()/

java併發程式設計Semaphore(訊號)的用法

Semaphore類其實就是synchronized關鍵字的升級版,這個類主要作用就是控制執行緒併發的數量,而在這方面synchronized就有點力不足了,接下來我們就開始先了解一下Semaphore的一些常用方法就注意細節。 在new 這個類的時候需要給這個類傳遞一個引

Java訊號控制多執行緒併發

package com.uusafe.thread2;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.S

IOS利用CommonDefaults儲存資料

一:本篇文章主要闡述如何利用ios儲存資料,並且獲取到資料; CommonDefaults.h #import <Foundation/Foundation.h> #import <CoreBluetooth/CoreBluetooth.h> @interf

毛毛Python進階路4——訊號、事件、佇列、生產者消費者模型、管道、程序池及其返回值!

毛毛Python進階之路4——訊號量、事件、佇列、生產者消費者模型、管道、程序池及其返回值! 1、訊號量 上次我們講到了鎖,可以控制某段程式在同一時間內只能被一個程序鎖訪問。現在我想被兩個程序訪問,或者更多怎麼辦了?訊號量就由此而生! 這就是訊號量做的事!這段程式我可以指定

CC2530基礎實驗採集光照模擬控制LED狀態

/* 當光照強的時候關閉LED燈 手捂著感測器跑馬燈 */ #include<iocc2530.h> int count=0; char output[8]; void Delay(unsigned char m) { int i=0,

從 0 開始學習 Linux 系列「24.訊號 semaphore」

訊號量 semaphore 訊號量(semaphore)與之前介紹的管道,訊息佇列的等 IPC 的思想不同,訊號量是一個計數器,用來為多個程序或執行緒提供對共享資料的訪問。 訊號量的原理 常用的訊號量是二值訊號量,它控制單個共享資源,初始值為 1,操作

linux程序通訊訊號燈(訊號,semaphore)

訊號燈通訊,和一般意義的通訊不大一樣,通訊一般是用來收發資料,而訊號燈卻是用來控制多程序訪問共享資源的,利用這一功能,訊號量也就可以用做程序同步(實際上是執行緒間同步)。訊號燈的當前值、有幾個程序在等待當前值變為0等等,這些資訊,可隨時用watch -n 0.1 ipcs -

oc GCD 訊號

訊號量是一個整形值並且具有一個初始計數值,並且支援兩個操作:訊號通知和等待。當一個訊號量被訊號通知,其計數會被增加。當一個執行緒在一個訊號量上等待時,執行緒會被阻塞,直至計數器大於零,然後執行緒會減少這個計數。 dispatch_semaphore_t m_semaph

iOS開發系列--並行開發(處理多個網路請求併發的情況)

概覽 大家都知道,在開發過程中應該儘可能減少使用者等待時間,讓程式儘可能快的完成運算。可是無論是哪種語言開發的程式最終往往轉換成組合語言進而解釋成機器碼來執行。但是機器碼是按順序執行的,一個複雜的多步操作只能一步步按順序逐個執行。改變這種狀況可以從兩個角度出發:對於單核處理

iOS使用dispatch_group實現分組併發網路請求

前言 在實際開發中我們通常會遇到這樣一種需求:某個頁面載入時通過網路請求獲得相應的資料,再做某些操作。有時候載入的內容需要通過好幾個請求的資料組合而成,比如有兩個請求A和B,我們通常為了省事,會將B請求放在A請求成功的回撥中發起,在B的成功回撥中將資料組合起來,這樣做有明顯的問題:

Swift基礎Demo包含重新整理,載入,網路請求,MVC

Swift中有一個Alamofire第三方是進行網路請求的,它是AFNetworking的作者寫的Swift形式,今天先介紹一下,利用pod匯入AFNetworking,SVProgressHUD,MJRefresh等第三方實現重新整理資料、載入更多、網路請求,同時使用了MV

OkHttp 3.12.1 釋出,輕的 Java 網路請求框架

   OkHttp 3.12.1 已釋出,這是一個小的修復版本,移除了重疊的 package-info.java 。#詳情 OkHttp 是一個適用於 Android 和 Java 應用的 HTTP 和 HTTP/2 客戶端。OkHttp 的使用非常簡單的,支援阻塞式的同步請求

利用logger列印完整的okhttp網路請求和響應日誌

我們公司在專案中使用的網路請求工具是Retrofit,底層封裝的是OkHttp,通常除錯網路介面時都會將網路請求和響應相關資料通過日誌的形式打印出來。OkHttp也提供了一個網路攔截器okhttp-logging-interceptor,通過它能攔截okhttp網路請求和響應所有相關資訊(請求行、請求頭、請

利用AS實現OKHttp協議的網路請求

用到的工具  Android StudioOKhttp相對於HttpURLConnection來說更加的方便和簡單實用,具體的方法如下:在使用okhttp的時候需要引用它開源的庫這裡給出一個路徑方便檢視最新的版本http://github.com/square/okhttp其