有并发的地方就存在线程安全问题,尤为是对于 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% 左右而且使用了更多的内存,可是与之对应的是咱们实现了一个并发安全的数组类型。
原文地址