一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - IOS - iOS中多網絡請求的線程安全詳解

iOS中多網絡請求的線程安全詳解

2021-04-02 17:08Adam Sharp IOS

這篇文章主要給大家介紹了關于iOS中多網絡請求的線程安全的相關資料文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。

前言

iOS 網絡編程有一種常見的場景是:我們需要并行處理二個請求并且在都成功后才能進行下一步處理。下面是部分常見的處理方式,但是在使用過程中也很容易出錯:

  • DispatchGroup:通過 GCD 機制將多個請求放到一個組內,然后通過 DispatchGroup.wait() DispatchGroup.notify() 進行成功后的處理。
  • OperationQueue:為每一個請求實例化一個 Operation 對象,然后將這些對象添加到 OperationQueue ,并且根據它們之間的依賴關系決定執行順序。
  • 同步 DispatchQueue:通過同步隊列和 NSLock 機制避免數據競爭,實現異步多線程中同步安全訪問。
  • 第三方類庫:Futures/Promises 以及響應式編程提供了更高層級的并發抽象。

在多年的實踐過程中,我意識到上面這些方法這些方法都存在一定的缺陷。另外,要想完全正確的使用這些類庫還是有些困難。

并發編程中的挑戰

使用并發的思維思考問題很困難:大多數時候,我們會按照讀故事的方式來閱讀代碼:從第一行到最后一行。如果代碼的邏輯不是線性的話,可能會給我們造成一定的理解難度。在單線程環境下,調試和跟蹤多個類和框架的程序執行已經是非常頭疼的一件事了,多線程環境下這種情況簡直不敢想象。

數據競爭問題:在多線程并發環境下,數據讀取操作是線程安全的而寫操作則是非線程安全。如果發生了多個線程同時對某個內存進行寫操作的話,則會發生數據競爭導致潛在數據錯誤。

理解多線程環境下的動態行為本身就不是一件容易的事,找出導致數據競爭的線程就更為麻煩。雖然我們可以通過互斥鎖機制解決數據競爭問題,但是對于可能修改的代碼來說互斥鎖機制的維護會是一件非常困難的事。

難以測試:并發環境下很多問題并不會在開發過程中顯現出來。雖然 Xcode 和 LLVM 提供了Thread Sanitizer 這類工具用于檢查這些問題,但是這些問題的調試和跟蹤依然存在很大的難度。因為并發環境下除了代碼本身的影響外,應用也會受到系統的影響。

處理并發情形的簡單方法

考慮到并發編程的復雜性,我們應該如何解決并行的多個請求?

最簡單的方式就是避免編寫并行代碼而是講多個請求線性的串聯在一起:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let session = URLSession.shared
 
session.dataTask(with: request1) { data, response, error in
 // check for errors
 // parse the response data
 
 session.dataTask(with: request2) { data, response error in
  // check for errors
  // parse the response data
 
  // if everything succeeded...
  callbackQueue.async {
   completionHandler(result1, result2)
  }
 }.resume()
}.resume()

為了保持代碼的簡潔,這里忽略了很多的細節處理,例如:錯誤處理以及請求取消操作。但是這樣將并無關聯的請求線性排序其實暗藏著一些問題。例如,如果服務端支持 HTTP/2 協議的話,我們就沒發利用 HTTP/2 協議中通過同一個鏈接處理多個請求的特性,而且線性處理也意味著我們沒有好好利用處理器的性能。

關于 URLSession 的錯誤認知

為了避免可能的數據競爭和線程安全問題,我將上面的代碼改寫為了嵌套請求。也就是說如果將其改為并發請求的話:請求將不能進行嵌套,兩個請求可能會對同一塊內存進行寫操作而數據競爭非常難以重現和調試。

解決改問題的一個可行辦法是通過鎖機制:在一段時間內只允許一個線程對共享內存進行寫操作。鎖機制的執行過程也非常簡單:請求鎖、執行代碼、釋放鎖。當然要想完全正確使用鎖機制還是有一些技巧的。

但是根據 URLSession 的文檔描述,這里有一個并發請求的更簡單解決方案。

?
1
2
3
init(configuration: URLSessionConfiguration,
   delegate: URLSessionDelegate?,
   delegateQueue queue: OperationQueue?)

[…]

queue : An operation queue for scheduling the delegate calls and completion handlers. The queue should be a serial queue, in order to ensure the correct ordering of callbacks. If nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.

這意味所有 URLSession 的實例對象包括 URLSession.shared 單例的回調并不會并發執行,除非你明確的傳人了一個并發隊列給參數 queue 。

URLSession 拓展并發支持

基于上面對 URLSession 的新認知,下面我們對其進行拓展讓它支持線程安全的并發請求(完成代碼地址)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum URLResult {
 case response(Data, URLResponse)
 case error(Error, Data?, URLResponse?)
}
 
extension URLSession {
 @discardableResult
 func get(_ url: URL, completionHandler: @escaping (URLResult) -> Void) -> URLSessionDataTask
}
 
// Example
 
let zen = URL(string: "https://api.github.com/zen")!
session.get(zen) { result in
 // process the result
}

首先,我們使用了一個簡單的 URLResult 枚舉來模擬我們可以在 URLSessionDataTask 回調中獲得的不同結果。該枚舉類型有利于我們簡化多個并發請求結果的處理。這里為了文章的簡潔并沒有貼出 URLSession.get(_:completionHandler:) 方法的完整實現,該方法就是使用 GET 方法請求對應的 URL 并自動執行 resume() 最后將執行結果封裝成 URLResult 對象。

?
1
2
3
4
@discardableResult
func get(_ left: URL, _ right: URL, completionHandler: @escaping (URLResult, URLResult) -> Void) -> (URLSessionDataTask, URLSessionDataTask) {
 
}

該段 API 代碼接受兩個 URL 參數并返回兩個 URLSessionDataTask 實例。下面代碼是函數實現的第一段:

?
1
2
precondition(delegateQueue.maxConcurrentOperationCount == 1,
 "URLSession's delegateQueue must be configured with a maxConcurrentOperationCount of 1.")

因為在實例化 URLSession 對象時依舊可以傳入并發的 OperationQueue 對象,所以這里我們需要使用上面這段代碼將這種情況排除掉。

?
1
2
3
4
5
6
var results: (left: URLResult?, right: URLResult?) = (nil, nil)
 
func continuation() {
 guard case let (left?, right?) = results else { return }
 completionHandler(left, right)
}

將這段代碼繼續添加到實現中,其中定義了一個表示返回結果的元組變量 results 。另外,我們還在函數內部定義了另一個工具函數用于檢查是否兩個請求都已經完成結果處理。

?
1
2
3
4
5
6
7
8
9
10
11
let left = get(left) { result in
 results.left = result
 continuation()
}
 
let right = get(right) { result in
 results.right = result
 continuation()
}
 
return (left, right)

最后將這段代碼追加到實現中,其中我們分別對兩個 URL 進行了請求并在請求都完成后一次返回了結果。值得注意的是這里我們通過兩次執行 continuation() 來判斷請求是否全部完成:

  • 第一次執行 continuation() 時因為其中一個請求并未完成結果為 nil 所以回調函數并不會執行。
  • 第二次執行的時候兩個請求全部完成,執行回調處理。

接下來我們可以通過簡單的請求來測試下這段代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extension URLResult {
 var string: String? {
  guard case let .response(data, _) = self,
  let string = String(data: data, encoding: .utf8)
  else { return nil }
  return string
 }
}
 
URLSession.shared.get(zen, zen) { left, right in
 guard case let (quote1?, quote2?) = (left.string, right.string)
 else { return }
 
 print(quote1, quote2, separator: "\n")
 // Approachable is better than simple.
 // Practicality beats purity.
}

并行悖論

我發現解決并行問題最簡單最優雅的方法就是盡可能的少使用并發編程,而且我們的處理器非常適合執行那些線性代碼。但是如果將大的代碼塊或任務拆分為多個并行執行的小代碼塊和任務將會讓代碼變得更加易讀和易維護。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。

作者:Adam Sharp,時間:2017/9/21

翻譯:BigNerdCoding, 如有錯誤歡迎指出。

原文鏈接:https://bignerdcoding.com/archives/55.html

延伸 · 閱讀

精彩推薦
  • IOSiOS實現控制屏幕常亮不變暗的方法示例

    iOS實現控制屏幕常亮不變暗的方法示例

    最近在工作中遇到了要將iOS屏幕保持常亮的需求,所以下面這篇文章主要給大家介紹了關于利用iOS如何實現控制屏幕常亮不變暗的方法,文中給出了詳細的...

    隨風13332021-04-02
  • IOSiOS開發技巧之狀態欄字體顏色的設置方法

    iOS開發技巧之狀態欄字體顏色的設置方法

    有時候我們需要根據不同的背景修改狀態欄字體的顏色,下面這篇文章主要給大家介紹了關于iOS開發技巧之狀態欄字體顏色的設置方法,文中通過示例代碼...

    夢想家-mxj8922021-05-10
  • IOSiOS中UILabel實現長按復制功能實例代碼

    iOS中UILabel實現長按復制功能實例代碼

    在iOS開發過程中,有時候會用到UILabel展示的內容,那么就設計到點擊UILabel復制它上面展示的內容的功能,也就是Label長按復制功能,下面這篇文章主要給大...

    devilx12792021-04-02
  • IOSiOS中滑動控制屏幕亮度和系統音量(附加AVAudioPlayer基本用法和Masonry簡單使用)

    iOS中滑動控制屏幕亮度和系統音量(附加AVAudioPlayer基本用法和

    這篇文章主要介紹了iOS中滑動控制屏幕亮度和系統音量(附加AVAudioPlayer基本用法和Masonry簡單使用)的相關資料,需要的朋友可以參考下...

    CodingFire13652021-02-26
  • IOS詳解iOS中多個網絡請求的同步問題總結

    詳解iOS中多個網絡請求的同步問題總結

    這篇文章主要介紹了詳解iOS中多個網絡請求的同步問題總結,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧...

    liang199111312021-03-15
  • IOSiOS開發之視圖切換

    iOS開發之視圖切換

    在iOS開發中視圖的切換是很頻繁的,獨立的視圖應用在實際開發過程中并不常見,除非你的應用足夠簡單。在iOS開發中常用的視圖切換有三種,今天我們將...

    執著丶執念5282021-01-16
  • IOSiOS中MD5加密算法的介紹和使用

    iOS中MD5加密算法的介紹和使用

    MD5加密是最常用的加密方法之一,是從一段字符串中通過相應特征生成一段32位的數字字母混合碼。對輸入信息生成唯一的128位散列值(32個字符)。這篇文...

    LYSNote5432021-02-04
  • IOSiOS自定義UICollectionViewFlowLayout實現圖片瀏覽效果

    iOS自定義UICollectionViewFlowLayout實現圖片瀏覽效果

    這篇文章主要介紹了iOS自定義UICollectionViewFlowLayout實現圖片瀏覽效果的相關資料,需要的朋友可以參考下...

    jiangamh8882021-01-11
主站蜘蛛池模板: 网友自拍偷拍 | 午夜影院免费观看视频 | 男人在女人下面狂躁 | 日本中文字幕黑人借宿影片 | 午夜影院一区二区三区 | 我和岳偷长篇小说 | 欧美特级特黄a大片免费 | 国产在线成人精品 | 日本午夜视频 | 国产农村一一级特黄毛片 | 全弄乱纶小说 | 男人和女人日比 | 韩国伦理hd | 成人深夜视频 | 日韩 欧美 国产 亚洲 中文 | a级在线看| 女仆色在线观看 | 欧洲老太玩小伙 | 国产欧美一区二区精品性色99 | 国产精品麻豆 | 五月激情丁香婷婷综合第九 | 国产精品免费久久久久影院 | 99视频福利 | 欧美日韩亚洲区久久综合 | 偷偷狠狠的日日高清完整视频 | 国产啪精品视频网给免丝袜 | 高清麻生希在线 | 欧美日韩一区二区中文字幕视频 | 欧美同性video| 国产激情视频 | 国产青草视频在线观看免费影院 | 欧洲一级 | 无限时间看片在线观看 | 爱情岛论坛自拍永久入口 | 激情另类国内一区二区视频 | 国产精品国产香蕉在线观看网 | 男女男精品视频免费观看 | 97热久久免费频精品99国产成人 | 国产综合久久久久久 | 免费我看视频在线观看 | 亚洲成人三级 |