有併發的地方就存在線程安全問題,尤爲是對於 Swift 這種尚未內置併發支持的語言來講線程安全問題更爲突出。下面咱們經過常見的數組操做來分析其中存在的線程問題,以及如何實現一個線程安全數組。html
由於沒法肯定執行順序,因此併發致使的問題通常都很難模擬和測試。不過咱們能夠經過下面這段代碼來模擬一個併發情形下致使的數據競爭問題。swift
var array = [Int]()
DispatchQueue.concurrentPerform(iterations: 1000) { index in
let last = array.last ?? 0
array.append(last + 1)
}複製代碼
這段代碼中咱們對數組 array 進行了 1000 次併發修改操做,雖然有些誇張可是它能很好的揭示一些併發環境下數組寫操做存在的一些問題。由於對於值類型來講 Swift 採用的是 Copy On Write 機制,因此在進行 Copy On Write 處理是可能數組已經被另外一個寫操做給修改了。這就形成了數組中元素和數據的丟失現象,以下:api
Unsafe loop count: 988.
Unsafe loop count: 991.
Unsafe loop count: 986.
Unsafe loop count: 995.複製代碼
這應該是你們都能想到的一種最多見處理方式。 因爲串行隊列每次都只能運行一個進程,因此即便有多個數組寫操做進程咱們也能確保資源的互斥訪問。這樣數組是從設計的併發進程安全的。數組
let queue = DispatchQueue(label: "SafeArrayQueue")
queue.async() {
// 寫操做
}
queue.sync() {
// 讀操做
}複製代碼
因爲寫操做並不須要返回操做結果,全部這裏能夠使用異步的方式進行。而對於讀操做來講則必須採用同步的方式實時返回操做結果。可是串行隊列有一個最爲明顯的缺陷:多個讀操做之間也是互斥的。很顯然這種方式太過粗暴存在明顯的性能問題,畢竟讀操做的頻率直覺上是要高過寫操做的。安全
採用併發隊列咱們就能夠很好的解決上面提到的多個讀操做的性能問題,不過隨之而來的就是寫操做的數據競爭。這與咱們在學習操做系統是的 讀者-做者 問題本質上是一類問題,咱們能夠經過共享互斥鎖來解決寫操做的數據競爭問題。對於 iOS 來講它就是 GCD 中的寫欄柵 barrier 機制。bash
let queue = DispatchQueue(label: "SafeArrayQueue", attributes: .concurrent)
queue.async(flags: .barrier) {
// 寫操做
}
queue.sync() {
// 讀操做
}複製代碼
上面代碼中咱們對異步的寫操做設置了 barrier 標示,這意味着在執行異步操做代碼的時候隊列不能執行其餘代碼。而對於同步的讀操做來講,因爲是併發隊列同時讀取數據並不會存在任何性能問題。併發
/// A thread-safe array.
public class SafeArray<Element> {
fileprivate let queue = DispatchQueue(label: "Com.BigNerdCoding.SafeArray", attributes: .concurrent)
fileprivate var array = [Element]()
}
// MARK: - Properties
public extension SafeArray {
var first: Element? {
var result: Element?
queue.sync { result = self.array.first }
return result
}
var last: Element? {
var result: Element?
queue.sync { result = self.array.last }
return result
}
var count: Int {
var result = 0
queue.sync { result = self.array.count }
return result
}
var isEmpty: Bool {
var result = false
queue.sync { result = self.array.isEmpty }
return result
}
var description: String {
var result = ""
queue.sync { result = self.array.description }
return result
}
}
// MARK: - 讀操做
public extension SafeArray {
func first(where predicate: (Element) -> Bool) -> Element? {
var result: Element?
queue.sync { result = self.array.first(where: predicate) }
return result
}
func filter(_ isIncluded: (Element) -> Bool) -> [Element] {
var result = [Element]()
queue.sync { result = self.array.filter(isIncluded) }
return result
}
func index(where predicate: (Element) -> Bool) -> Int? {
var result: Int?
queue.sync { result = self.array.index(where: predicate) }
return result
}
func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> [Element] {
var result = [Element]()
queue.sync { result = self.array.sorted(by: areInIncreasingOrder) }
return result
}
func flatMap<ElementOfResult>(_ transform: (Element) -> ElementOfResult?) -> [ElementOfResult] {
var result = [ElementOfResult]()
queue.sync { result = self.array.flatMap(transform) }
return result
}
func forEach(_ body: (Element) -> Void) {
queue.sync { self.array.forEach(body) }
}
func contains(where predicate: (Element) -> Bool) -> Bool {
var result = false
queue.sync { result = self.array.contains(where: predicate) }
return result
}
}
// MARK: - 寫操做
public extension SafeArray {
func append( _ element: Element) {
queue.async(flags: .barrier) {
self.array.append(element)
}
}
func append( _ elements: [Element]) {
queue.async(flags: .barrier) {
self.array += elements
}
}
func insert( _ element: Element, at index: Int) {
queue.async(flags: .barrier) {
self.array.insert(element, at: index)
}
}
func remove(at index: Int, completion: ((Element) -> Void)? = nil) {
queue.async(flags: .barrier) {
let element = self.array.remove(at: index)
DispatchQueue.main.async {
completion?(element)
}
}
}
func remove(where predicate: @escaping (Element) -> Bool, completion: ((Element) -> Void)? = nil) {
queue.async(flags: .barrier) {
guard let index = self.array.index(where: predicate) else { return }
let element = self.array.remove(at: index)
DispatchQueue.main.async {
completion?(element)
}
}
}
func removeAll(completion: (([Element]) -> Void)? = nil) {
queue.async(flags: .barrier) {
let elements = self.array
self.array.removeAll()
DispatchQueue.main.async {
completion?(elements)
}
}
}
}
public extension SafeArray {
subscript(index: Int) -> Element? {
get {
var result: Element?
queue.sync {
guard self.array.startIndex..<self.array.endIndex ~= index else { return }
result = self.array[index]
}
return result
}
set {
guard let newValue = newValue else { return }
queue.async(flags: .barrier) {
self.array[index] = newValue
}
}
}
}
// MARK: - Equatable
public extension SafeArray where Element: Equatable {
func contains(_ element: Element) -> Bool {
var result = false
queue.sync { result = self.array.contains(element) }
return result
}
}
// MARK: - 自定義操做符
public extension SynchronizedArray {
static func +=(left: inout SynchronizedArray, right: Element) {
left.append(right)
}
static func +=(left: inout SynchronizedArray, right: [Element]) {
left.append(right)
}
}複製代碼
經過 filePrivate 屬性 array 和 queue , SafeArray 成功的實現了大多數數組經常使用功能,更爲關鍵的是該類型併發安全:全部的寫操做都經過 barrier 方式的異步進行,而讀操做則與內置 Array 沒有什麼區別。app
須要注意的是:咱們使用一樣的方式能夠實現併發安全的 Dictionary 相似:SynchronizedDictionary。異步
接下來,咱們能夠對傳統的非併發安全數組和 SafeArray 進行如下比較:async
import Foundation
import PlaygroundSupport
// Thread-unsafe array
do {
var array = [Int]()
var iterations = 1000
let start = Date().timeIntervalSince1970
DispatchQueue.concurrentPerform(iterations: iterations) { index in
let last = array.last ?? 0
array.append(last + 1)
DispatchQueue.global().sync {
iterations -= 1
// Final loop
guard iterations <= 0 else { return }
let message = String(format: "Unsafe loop took %.3f seconds, count: %d.",
Date().timeIntervalSince1970 - start,
array.count)
print(message)
}
}
}
// Thread-safe array
do {
var array = SafeArray<Int>()
var iterations = 1000
let start = Date().timeIntervalSince1970
DispatchQueue.concurrentPerform(iterations: iterations) { index in
let last = array.last ?? 0
array.append(last + 1)
DispatchQueue.global().sync {
iterations -= 1
// Final loop
guard iterations <= 0 else { return }
let message = String(format: "Safe loop took %.3f seconds, count: %d.",
Date().timeIntervalSince1970 - start,
array.count)
print(message)
}
}
}
PlaygroundPage.current.needsIndefiniteExecution = true複製代碼
獲得的輸出可能以下:
Unsafe loop took 1.031 seconds, count: 989.
Safe loop took 1.363 seconds, count: 1000.複製代碼
雖然因爲使用了 GCD 機制致使速度慢了 30% 左右而且使用了更多的內存,可是與之對應的是咱們實現了一個併發安全的數組類型。
原文地址