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

作者: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. 【深度学习系列】手写数字识别卷积神经--卷积神经网络CNN原理详解(一)

    上篇文章我们给出了用paddlepaddle来做手写数字识别的示例,并对网络结构进行到了调整,提高了识别的精度.有的同学表示不是很理解原理,为什么传统的机器学习算法,简单的神经网络(如多层感知机)都可 ...

  2. 暑假练习赛 006 E Vanya and Label(数学)

    Vanya and LabelCrawling in process... Crawling failed Time Limit:1000MS     Memory Limit:262144KB    ...

  3. 0_Simple__simpleCallback

    学习回调函数的基本概念,并在CUDA的任务流中插入基于CPU的主机函数,作为回调函数使用. ▶ 源代码:没有用到的部分被注释起来了 /*multithreading.h*/ #ifndef MULTI ...

  4. javascript中自定义事件

    自定义事件:用户可以指定事件类型,这个类型实际上就是一个字符串,然后为这个类型的事件指定事件处理函数,可以注册多个事件处理函数(用数组管理),调用时,从多个事件处理函数中找到再调用. function ...

  5. 拓扑排序 HDU - 5695

    众所周知,度度熊喜欢各类体育活动. 今天,它终于当上了梦寐以求的体育课老师.第一次课上,它发现一个有趣的事情.在上课之前,所有同学要排成一列, 假设最开始每个人有一个唯一的ID,从1到NN,在排好队之 ...

  6. 10个鲜为人知的C#关键字

    在正式开始之前,我需要先声明:这些关键字对于偏向底层的程序员更加耳熟能详,对这些关键字不了解并不影响你作为一个合格的程序员. 这意味着这些关键字会让你在编写程序时得到更好的代码质量和可读性,enjoy ...

  7. VMware下Linux网络配置局域网和外网访问

    要使用Linux系统很重要的一个操作就是使Linux系统能够访问互联网,只有Linux系统能够访问互联网才能够去下载很多自己所需要的资源,如果不能访问互联网那么使用Linux系统往往会卡在这一步,假设 ...

  8. struts2的简单执行过程

    struts2是最近刚学的一个框架,想通过写篇文章来加深下印象,这也是本篇博文产生的由来,下面进入正题 Struts2本身是一个挺简单的框架,我们通过写一个登陆的过程来具体描述下其执行过程 1.首先我 ...

  9. 封装一个通过class获取元素的方法--我的JS原生库(1)

    function getByClass(oParent,sClass){ var aEle = oParent.getElementsByTagName('*'); var result = []; ...

  10. 多个Fragment的分开管理方案

    当项目里有多个Fragment的时候  我们希望让Fragment有个分类 并且展示的时候不会混淆在一起 例如:项目中导航栏有三个按钮   每个按钮对应一种分类的布局,每个分类的布局中有多个Fragm ...