有并发的地方就存在线程安全问题,尤其是对于 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 处理是可能数组已经被另一个写 *** 作给修改了。这就造成了数组中元素和数据的丢失现象,如下:
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 机制。
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: - PropertIEspublic 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: - Equatablepublic 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 没有什么区别。
需要注意的是:我们使用同样的方式可以实现并发安全的 Dictionary 类似:SynchronizedDictionary。
接下来,我们可以对传统的非并发安全数组和 SafeArray 进行以下比较:
import Foundationimport PlaygroundSupport // Thread-unsafe arraydo { 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 arraydo { 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, 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% 左右并且使用了更多的内存,但是与之对应的是我们实现了一个并发安全的数组类型。
总结以上是内存溢出为你收集整理的Swift 线程安全数组全部内容,希望文章能够帮你解决Swift 线程安全数组所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)