Swift 线程安全数组
欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~
有并发的地方就存在线程安全问题,尤其是对于 Swift 这种还没有内置并发支持的语言来说线程安全问题更为突出。下面我们通过常见的数组操作来分析其中存在的线程问题,以及如何实现一个线程安全数组。
问题所在
因为无法确定执行顺序,所以并发导致的问题一般都很难模拟和测试。不过我们可以通过下面这段代码来模拟一个并发情形下导致的数据竞争问题。
var array = [Int]()
DispatchQueue.concurrentPerform(iterations: ) { index in
let last = array.last ??
array.append(last + )
}
这段代码中我们对数组 array 进行了 1000 次并发修改操作,虽然有些夸张但是它能很好的揭示一些并发环境下数组写操作存在的一些问题。因为对于值类型来说 Swift 采用的是 Copy On Write 机制,所以在进行 Copy On Write 处理是可能数组已经被另一个写操作给修改了。这就造成了数组中元素和数据的丢失现象,如下:
Unsafe loop count: .
Unsafe loop count: .
Unsafe loop count: .
Unsafe loop count: .
串行队列
这应该是大家都能想到的一种最常见处理方式。 由于串行队列每次都只能运行一个进程,所以即使有多个数组写操作进程我们也能确保资源的互斥访问。这样数组是从设计的并发进程安全的。
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: - 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 =
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 没有什么区别。
需要注意的是:我们使用同样的方式可以实现并发安全的 Dictionary 类似:SynchronizedDictionary。
接下来,我们可以对传统的非并发安全数组和 SafeArray 进行以下比较:
import Foundation
import PlaygroundSupport // Thread-unsafe array
do {
var array = [Int]()
var iterations =
let start = Date().timeIntervalSince1970 DispatchQueue.concurrentPerform(iterations: iterations) { index in
let last = array.last ??
array.append(last + ) DispatchQueue.global().sync {
iterations -= // Final loop
guard iterations <= 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 =
let start = Date().timeIntervalSince1970 DispatchQueue.concurrentPerform(iterations: iterations) { index in
let last = array.last ??
array.append(last + ) DispatchQueue.global().sync {
iterations -= // Final loop
guard iterations <= 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: .
Safe loop took 1.363 seconds, count: .
虽然由于使用了 GCD 机制导致速度慢了 30% 左右并且使用了更多的内存,但是与之对应的是我们实现了一个并发安全的数组类型。
相关阅读
Swift 线程安全数组的更多相关文章
- Swift中的数组
学习来自<极客学院:Swift中的字符串和集合> 工具:Xcode6.4 直接上基础的示例代码,多敲多体会就会有收获:百看不如一敲,一敲就会 import Foundation //数组: ...
- Swift学习—字符串&数组&字典
字符串 OC和Swift中字符串的区别 在OC中字符串类型时NSString,在Swift中字符串类型是String OC中字符串@"",Swift中字符串"" ...
- [Swift 语法点滴]——数组参数
Swift语言一如既往的继承了苹果公司卓尔不群的奇葩思维方式,总是要弄得跟别的语言不一样,才能显出它的特殊 比如用数组作为参数上,这格式实在是没有试出来,找了stackoverflow,才找到相应信息 ...
- Swift不可变数组
Objective-C编写了2个不同的类来区分不可变数组(NSArray)和可变数组(NSMutableArray): Swift通过使用常量和变量来区分不可变数组和可变数组. 只要将数组定义为常量, ...
- swift学习之数组
首先数组的定义:以有序的方式存储同样类型的值 (1)数组的简写(shorthand)语法 你能够通过Array<Element>,在这里,Element时数组存储元素的值的类型.也能够通过 ...
- Swift初探02 数组、集合、元组、字典
数组.集合.元组.字典 每一门语言基本都会有这些数据结构,swift也不例外,而作为一门现代的语言,swift提供了很多的现成的方法给我们选择. 一.数组 01 数组的定义 // 三种声明方式 var ...
- Swift 循环、数组 字典的遍历
import Foundation // 数组声明 var arr = [String]() // 数组循环添加项 ...{ arr.append("Item \(index)") ...
- 初学swift笔记-数组、字典、元组(三)
数组的使用.字典的使用.元组的使用 import Foundation //1.定义数组 //集合数据 数组.字典 ,,,]//常用定义 ,,,]//常用定义 ,,,]//范型定义 ,,,] arr_ ...
- [Swift]LeetCode189. 旋转数组 | Rotate Array
Given an array, rotate the array to the right by k steps, where k is non-negative. Example 1: Input: ...
随机推荐
- 事件轮询中的task与microtask
event loop 网上看到的一篇文章,关于介绍task和Tasks, microtasks, queues and schedules,尝试简单翻译一下写进来吧! 原文地址:https://jak ...
- KICKSTART无人值守安装
1.1 环境说明 [root@test ~]# cat /etc/redhat-release CentOS release 6.9 (Final) [root@test ~]# uname -r - ...
- SVN Upgrade working copy
出现这个的原因是因为你机器上安装的svn客户端版本过高,而你从别的地方拷贝代码中的svn版本过低,点击SVN Upgrade working copy可以将代码中的svn版本升级.
- js 添加事件 attachEvent 和 addEventListener 的区别
1.addEventListener 适用w3c标准方法addEventListener绑定事件,如下,事件的执行顺序和绑定顺序一致,执行顺序为method1->method2->meth ...
- HTML学习笔记 CSS表格及轮廓案例 第八节 (原创)参考使用表
#tb, tb1, tr, th, td { border: 5px solid blue; /*加边框*/ padding: 5px; /*内边距*/ } #tb1 { border-collaps ...
- Problem C: 是否回文数?
Description 定义Data类,有一个int类型的属性.定义其构造函数.setValue函数和isPalindrome函数,其中setValue函数用于设置属性值,isPalindrome用于 ...
- rewirte 规则
Nginx Rewrite Rewirte 规则也称为规则重写,主要功能是实现浏览器访问 HTTP URL 的跳转,其正则 表达式是基于 Perl 语言.通常而言,几乎所有的 WEB 服务器均可以支持 ...
- ListView中点击Item没有任何响应
不多说,上代码:如下图 红色方框的东西, android:descendantFocusability=”blocksDescendants” 如果不添加的话,在android的8.0手机上点击没有响 ...
- 深度学习之tensorflow (一)
一.TensorFlow简介 1.TensorFlow定义: tensor :张量,N维数组 Flow : 流,基于数据流图的计算 TensorFlow : 张量从图像的一端流动到另一端的计算 ...
- Ubuntu初始化设置
1.安装chrome sudo apt-get install chromium-browser sudo apt-get install pepperflashplugin-nonfree sudo ...