欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~

作者:BigNerdCoding

有并发的地方就存在线程安全问题,尤其是对于 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 属性 arrayqueue , 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 线程安全数组的更多相关文章

  1. Swift中的数组

    学习来自<极客学院:Swift中的字符串和集合> 工具:Xcode6.4 直接上基础的示例代码,多敲多体会就会有收获:百看不如一敲,一敲就会 import Foundation //数组: ...

  2. Swift学习—字符串&数组&字典

    字符串 OC和Swift中字符串的区别 在OC中字符串类型时NSString,在Swift中字符串类型是String OC中字符串@"",Swift中字符串"" ...

  3. [Swift 语法点滴]——数组参数

    Swift语言一如既往的继承了苹果公司卓尔不群的奇葩思维方式,总是要弄得跟别的语言不一样,才能显出它的特殊 比如用数组作为参数上,这格式实在是没有试出来,找了stackoverflow,才找到相应信息 ...

  4. Swift不可变数组

    Objective-C编写了2个不同的类来区分不可变数组(NSArray)和可变数组(NSMutableArray): Swift通过使用常量和变量来区分不可变数组和可变数组. 只要将数组定义为常量, ...

  5. swift学习之数组

    首先数组的定义:以有序的方式存储同样类型的值 (1)数组的简写(shorthand)语法 你能够通过Array<Element>,在这里,Element时数组存储元素的值的类型.也能够通过 ...

  6. Swift初探02 数组、集合、元组、字典

    数组.集合.元组.字典 每一门语言基本都会有这些数据结构,swift也不例外,而作为一门现代的语言,swift提供了很多的现成的方法给我们选择. 一.数组 01 数组的定义 // 三种声明方式 var ...

  7. Swift 循环、数组 字典的遍历

    import Foundation // 数组声明 var arr = [String]() // 数组循环添加项 ...{ arr.append("Item \(index)") ...

  8. 初学swift笔记-数组、字典、元组(三)

    数组的使用.字典的使用.元组的使用 import Foundation //1.定义数组 //集合数据 数组.字典 ,,,]//常用定义 ,,,]//常用定义 ,,,]//范型定义 ,,,] arr_ ...

  9. [Swift]LeetCode189. 旋转数组 | Rotate Array

    Given an array, rotate the array to the right by k steps, where k is non-negative. Example 1: Input: ...

随机推荐

  1. xml入门视频

    XML 是可扩展标记语言(Extensible Markup Language)的缩写,其中的 标记(markup)是关键部分.您可以创建内容,然后使用限定标记标记它,从而使每个单词.短语或块成为可识 ...

  2. Linux_服务器_01_查看公网IP

    在linux终端提示符下,输入以下命令: 精选: curl icanhazip.com/curl ifconfig.mecurl ipecho.net/plain 可以看到下图已经查询到公网IP地址了 ...

  3. win10 安装Node.js 报错:2503

    解决方法: 使用管理员打开CMD

  4. Windows环境下多线程编程原理与应用读书笔记(1)————基本概念

    自从学了操作系统知识后,我就对多线程比较感兴趣,总想让自己写一些有关多线程的程序代码,但一直以来,发现自己都没怎么好好的去全面学习这方面的知识,仅仅是完成了操作系统课程上的小程序,对多线程的理解也不是 ...

  5. defer与async

    defer:该属性指定的脚本不会修改DOM,因此代码可以安全的延迟执行. 含defer属性的script标签可以放在任何位置,在页面解析到该script标签时,开始下载脚本,但不会执行脚本,直至DOM ...

  6. linux 安装wordpress 无故往外发送大量垃圾邮件

    linux 安装wordpress 无故往外发送大量垃圾邮件 始末 表现出来的现象就是, 网站运行没多久,mysql服务就挂了,重启也无法启动起来,提示 No such file or dicrion ...

  7. 【3】测试搭建成功的单机hadoop环境

    1.关闭防火墙service iptables stop,(已经设置开机关闭的忽略) 2.进入hadoop目录,修改hadoop配置文件(4个) core-site.xml(核心配置,fs.defau ...

  8. SSM 五:Spring核心概念

    第五章:Spring核心概念 一.Spring Ioc 优点: 1.低侵入式设计 2.独立于各种应用服务器 3.依赖注入特性将组建关系透明化,降低耦合度 4.面向切面编程的特性允许将通用性任务集中式处 ...

  9. 二:SQL映射文件

    二:SQL映射文件 1.SQL映射文件: (1)mapper:映射文件的根元素节点,只有一个属性namespace(命名空间) 作用:用于区分不同的mapper全局唯一 绑定dao接口即面向接口编程, ...

  10. shell脚本 expect 实现自动登陆

    vi auto_ssh.exp #!/usr/bin/expect   set ipaddress "123.227.159.159" set passwd "你的密码& ...