ios 在 Swift 中创建线程安全数组
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28191079/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Create thread safe array in Swift
提问by patrickS
I have a threading problem in Swift. I have an array with some objects in it. Over a delegate the class gets new objects about every second. After that I have to check if the objects are already in the array, so I have to update the object, otherwise I have to delete / add the new object.
我在 Swift 中有一个线程问题。我有一个数组,里面有一些对象。通过委托,该类大约每秒都会获得新对象。之后我必须检查对象是否已经在数组中,所以我必须更新对象,否则我必须删除/添加新对象。
If I add a new object I have to fetch some data over the network first. This is handelt via a block.
如果我添加一个新对象,我必须首先通过网络获取一些数据。这是通过块的handelt。
Now my problem is, how to I synchronic this tasks?
现在我的问题是,如何同步这些任务?
I have tried a dispatch_semaphore, but this one blocks the UI, until the block is finished.
我尝试了一个 dispatch_semaphore,但是这个阻塞了 UI,直到阻塞完成。
I have also tried a simple bool variable, which checks if the block is currently executed and skips the compare method meanwhile.
我还尝试了一个简单的 bool 变量,它检查当前是否正在执行块并同时跳过比较方法。
But both methods are not ideal.
但是这两种方法都不理想。
What's the best way to manage the array, I don't wanna have duplicate data in the array.
管理阵列的最佳方法是什么,我不想在阵列中有重复的数据。
回答by skim
Kirsteinsis correct, but you don't always need to use dispatch queue. You can use:
Kirsteins是正确的,但您并不总是需要使用调度队列。您可以使用:
objc_sync_enter(array)
// manipulate the array
objc_sync_exit(array)
This ought to do the trick. For added bonus, you can create a function to use whenever you need thread safety:
这应该可以解决问题。为了获得额外的好处,您可以创建一个函数,以便在需要线程安全时使用:
func sync(lock: NSObject, closure: () -> Void) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
...
var list = NSMutableArray()
sync (list) {
list.addObject("something")
}
Notice that I have changed AnyObject
to NSObject
. In Swift collection types are implemented as struct
s and they are passed by value, so I am guessing it would be safer to work with mutablecollection classes that are passed by referencewhen using the convenient sync
function.
请注意,我已更改AnyObject
为NSObject
. 在 Swift 中,集合类型被实现为struct
s 并且它们通过 value 传递,所以我猜在使用方便的函数时使用通过引用传递的可变集合类会更安全。sync
Update for Swift
Swift 更新
The recommended pattern for thread-safe access is using dispatch barrier
:
线程安全访问的推荐模式是使用 dispatch barrier
:
let queue = DispatchQueue(label: "thread-safe-obj", attributes: .concurrent)
// write
queue.async(flags: .barrier) {
// perform writes on data
}
// read
var value: ValueType!
queue.sync {
// perform read and assign value
}
return value
回答by rmooney
Kirsteins's answer is correct, but for convenience, I've updated that answer with Amol Chaudhari and Rob's suggestions for using a concurrent queue with async barrier to allow concurrent reads but block on writes.
Kirsteins 的答案是正确的,但为了方便起见,我已经使用 Amol Chaudhari 和 Rob 的建议更新了该答案,建议使用具有异步屏障的并发队列来允许并发读取但阻止写入。
I've also wrapped some other array functions that were useful to me.
我还封装了一些对我有用的其他数组函数。
public class SynchronizedArray<T> {
private var array: [T] = []
private let accessQueue = dispatch_queue_create("SynchronizedArrayAccess", DISPATCH_QUEUE_CONCURRENT)
public func append(newElement: T) {
dispatch_barrier_async(self.accessQueue) {
self.array.append(newElement)
}
}
public func removeAtIndex(index: Int) {
dispatch_barrier_async(self.accessQueue) {
self.array.removeAtIndex(index)
}
}
public var count: Int {
var count = 0
dispatch_sync(self.accessQueue) {
count = self.array.count
}
return count
}
public func first() -> T? {
var element: T?
dispatch_sync(self.accessQueue) {
if !self.array.isEmpty {
element = self.array[0]
}
}
return element
}
public subscript(index: Int) -> T {
set {
dispatch_barrier_async(self.accessQueue) {
self.array[index] = newValue
}
}
get {
var element: T!
dispatch_sync(self.accessQueue) {
element = self.array[index]
}
return element
}
}
}
UPDATEThis is the same code, updated for Swift3.
更新这是为 Swift3 更新的相同代码。
public class SynchronizedArray<T> {
private var array: [T] = []
private let accessQueue = DispatchQueue(label: "SynchronizedArrayAccess", attributes: .concurrent)
public func append(newElement: T) {
self.accessQueue.async(flags:.barrier) {
self.array.append(newElement)
}
}
public func removeAtIndex(index: Int) {
self.accessQueue.async(flags:.barrier) {
self.array.remove(at: index)
}
}
public var count: Int {
var count = 0
self.accessQueue.sync {
count = self.array.count
}
return count
}
public func first() -> T? {
var element: T?
self.accessQueue.sync {
if !self.array.isEmpty {
element = self.array[0]
}
}
return element
}
public subscript(index: Int) -> T {
set {
self.accessQueue.async(flags:.barrier) {
self.array[index] = newValue
}
}
get {
var element: T!
self.accessQueue.sync {
element = self.array[index]
}
return element
}
}
}
回答by Kirsteins
My approach to this problem was using serial dispatch queue, to synchronise access to boxed array. It will block the thread when you try to get the value at index and queue is really busy, but that's the problem with locks as well.
我解决这个问题的方法是使用串行调度队列来同步对盒装数组的访问。当您尝试获取索引处的值并且队列非常忙时,它会阻塞线程,但这也是锁的问题。
public class SynchronizedArray<T> {
private var array: [T] = []
private let accessQueue = dispatch_queue_create("SynchronizedArrayAccess", DISPATCH_QUEUE_SERIAL)
public func append(newElement: T) {
dispatch_async(self.accessQueue) {
self.array.append(newElement)
}
}
public subscript(index: Int) -> T {
set {
dispatch_async(self.accessQueue) {
self.array[index] = newValue
}
}
get {
var element: T!
dispatch_sync(self.accessQueue) {
element = self.array[index]
}
return element
}
}
}
var a = SynchronizedArray<Int>()
a.append(1)
a.append(2)
a.append(3)
// can be empty as this is non-thread safe access
println(a.array)
// thread-safe synchonized access
println(a[0])
println(a[1])
println(a[2])
回答by nbloqs
A minor detail: In Swift 3 (at least in Xcode 8 Beta 6), the syntax for queues changed significantly. The important changes to @Kirsteins' answer will be:
一个小细节:在 Swift 3 中(至少在 Xcode 8 Beta 6 中),队列的语法发生了显着变化。@Kirsteins 回答的重要变化是:
private let accessQueue = DispatchQueue(label: "SynchronizedArrayAccess")
txAccessQueue.async() {
// Your async code goes here...
}
txAccessQueue.sync() {
// Your sync code goes here...
}
回答by Vasily Bodnarchuk
Details
细节
- Xcode 10.1 (10B61), Swift 4.2
- Xcode 10.2.1 (10E1001), Swift 5
- Xcode 10.1 (10B61),Swift 4.2
- Xcode 10.2.1 (10E1001),Swift 5
Solution
解决方案
import Foundation
// https://developer.apple.com/documentation/swift/rangereplaceablecollection
struct AtomicArray<T>: RangeReplaceableCollection {
typealias Element = T
typealias Index = Int
typealias SubSequence = AtomicArray<T>
typealias Indices = Range<Int>
fileprivate var array: Array<T>
var startIndex: Int { return array.startIndex }
var endIndex: Int { return array.endIndex }
var indices: Range<Int> { return array.indices }
func index(after i: Int) -> Int { return array.index(after: i) }
private var semaphore = DispatchSemaphore(value: 1)
fileprivate func _wait() { semaphore.wait() }
fileprivate func _signal() { semaphore.signal() }
}
// MARK: - Instance Methods
extension AtomicArray {
init<S>(_ elements: S) where S : Sequence, AtomicArray.Element == S.Element {
array = Array<S.Element>(elements)
}
init() { self.init([]) }
init(repeating repeatedValue: AtomicArray.Element, count: Int) {
let array = Array(repeating: repeatedValue, count: count)
self.init(array)
}
}
// MARK: - Instance Methods
extension AtomicArray {
public mutating func append(_ newElement: AtomicArray.Element) {
_wait(); defer { _signal() }
array.append(newElement)
}
public mutating func append<S>(contentsOf newElements: S) where S : Sequence, AtomicArray.Element == S.Element {
_wait(); defer { _signal() }
array.append(contentsOf: newElements)
}
func filter(_ isIncluded: (AtomicArray.Element) throws -> Bool) rethrows -> AtomicArray {
_wait(); defer { _signal() }
let subArray = try array.filter(isIncluded)
return AtomicArray(subArray)
}
public mutating func insert(_ newElement: AtomicArray.Element, at i: AtomicArray.Index) {
_wait(); defer { _signal() }
array.insert(newElement, at: i)
}
mutating func insert<S>(contentsOf newElements: S, at i: AtomicArray.Index) where S : Collection, AtomicArray.Element == S.Element {
_wait(); defer { _signal() }
array.insert(contentsOf: newElements, at: i)
}
mutating func popLast() -> AtomicArray.Element? {
_wait(); defer { _signal() }
return array.popLast()
}
@discardableResult mutating func remove(at i: AtomicArray.Index) -> AtomicArray.Element {
_wait(); defer { _signal() }
return array.remove(at: i)
}
mutating func removeAll() {
_wait(); defer { _signal() }
array.removeAll()
}
mutating func removeAll(keepingCapacity keepCapacity: Bool) {
_wait(); defer { _signal() }
array.removeAll()
}
mutating func removeAll(where shouldBeRemoved: (AtomicArray.Element) throws -> Bool) rethrows {
_wait(); defer { _signal() }
try array.removeAll(where: shouldBeRemoved)
}
@discardableResult mutating func removeFirst() -> AtomicArray.Element {
_wait(); defer { _signal() }
return array.removeFirst()
}
mutating func removeFirst(_ k: Int) {
_wait(); defer { _signal() }
array.removeFirst(k)
}
@discardableResult mutating func removeLast() -> AtomicArray.Element {
_wait(); defer { _signal() }
return array.removeLast()
}
mutating func removeLast(_ k: Int) {
_wait(); defer { _signal() }
array.removeLast(k)
}
@inlinable public func forEach(_ body: (Element) throws -> Void) rethrows {
_wait(); defer { _signal() }
try array.forEach(body)
}
mutating func removeFirstIfExist(where shouldBeRemoved: (AtomicArray.Element) throws -> Bool) {
_wait(); defer { _signal() }
guard let index = try? array.firstIndex(where: shouldBeRemoved) else { return }
array.remove(at: index)
}
mutating func removeSubrange(_ bounds: Range<Int>) {
_wait(); defer { _signal() }
array.removeSubrange(bounds)
}
mutating func replaceSubrange<C, R>(_ subrange: R, with newElements: C) where C : Collection, R : RangeExpression, T == C.Element, AtomicArray<Element>.Index == R.Bound {
_wait(); defer { _signal() }
array.replaceSubrange(subrange, with: newElements)
}
mutating func reserveCapacity(_ n: Int) {
_wait(); defer { _signal() }
array.reserveCapacity(n)
}
public var count: Int {
_wait(); defer { _signal() }
return array.count
}
public var isEmpty: Bool {
_wait(); defer { _signal() }
return array.isEmpty
}
}
// MARK: - Get/Set
extension AtomicArray {
// Single action
func get() -> [T] {
_wait(); defer { _signal() }
return array
}
mutating func set(array: [T]) {
_wait(); defer { _signal() }
self.array = array
}
// Multy actions
mutating func get(closure: ([T])->()) {
_wait(); defer { _signal() }
closure(array)
}
mutating func set(closure: ([T]) -> ([T])) {
_wait(); defer { _signal() }
array = closure(array)
}
}
// MARK: - Subscripts
extension AtomicArray {
subscript(bounds: Range<AtomicArray.Index>) -> AtomicArray.SubSequence {
get {
_wait(); defer { _signal() }
return AtomicArray(array[bounds])
}
}
subscript(bounds: AtomicArray.Index) -> AtomicArray.Element {
get {
_wait(); defer { _signal() }
return array[bounds]
}
set(value) {
_wait(); defer { _signal() }
array[bounds] = value
}
}
}
// MARK: - Operator Functions
extension AtomicArray {
static func + <Other>(lhs: Other, rhs: AtomicArray) -> AtomicArray where Other : Sequence, AtomicArray.Element == Other.Element {
return AtomicArray(lhs + rhs.get())
}
static func + <Other>(lhs: AtomicArray, rhs: Other) -> AtomicArray where Other : Sequence, AtomicArray.Element == Other.Element {
return AtomicArray(lhs.get() + rhs)
}
static func + <Other>(lhs: AtomicArray, rhs: Other) -> AtomicArray where Other : RangeReplaceableCollection, AtomicArray.Element == Other.Element {
return AtomicArray(lhs.get() + rhs)
}
static func + (lhs: AtomicArray<Element>, rhs: AtomicArray<Element>) -> AtomicArray {
return AtomicArray(lhs.get() + rhs.get())
}
static func += <Other>(lhs: inout AtomicArray, rhs: Other) where Other : Sequence, AtomicArray.Element == Other.Element {
lhs._wait(); defer { lhs._signal() }
lhs.array += rhs
}
}
// MARK: - CustomStringConvertible
extension AtomicArray: CustomStringConvertible {
var description: String {
_wait(); defer { _signal() }
return "\(array)"
}
}
// MARK: - Equatable
extension AtomicArray where Element : Equatable {
func split(separator: Element, maxSplits: Int, omittingEmptySubsequences: Bool) -> [ArraySlice<Element>] {
_wait(); defer { _signal() }
return array.split(separator: separator, maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences)
}
func firstIndex(of element: Element) -> Int? {
_wait(); defer { _signal() }
return array.firstIndex(of: element)
}
func lastIndex(of element: Element) -> Int? {
_wait(); defer { _signal() }
return array.lastIndex(of: element)
}
func starts<PossiblePrefix>(with possiblePrefix: PossiblePrefix) -> Bool where PossiblePrefix : Sequence, Element == PossiblePrefix.Element {
_wait(); defer { _signal() }
return array.starts(with: possiblePrefix)
}
func elementsEqual<OtherSequence>(_ other: OtherSequence) -> Bool where OtherSequence : Sequence, Element == OtherSequence.Element {
_wait(); defer { _signal() }
return array.elementsEqual(other)
}
func contains(_ element: Element) -> Bool {
_wait(); defer { _signal() }
return array.contains(element)
}
static func != (lhs: AtomicArray<Element>, rhs: AtomicArray<Element>) -> Bool {
lhs._wait(); defer { lhs._signal() }
rhs._wait(); defer { rhs._signal() }
return lhs.array != rhs.array
}
static func == (lhs: AtomicArray<Element>, rhs: AtomicArray<Element>) -> Bool {
lhs._wait(); defer { lhs._signal() }
rhs._wait(); defer { rhs._signal() }
return lhs.array == rhs.array
}
}
Usage sample 1
使用示例 1
import Foundation
// init
var array = AtomicArray<Int>()
print(array)
array = AtomicArray(repeating: 0, count: 5)
print(array)
array = AtomicArray([1,2,3,4,5,6,7,8,9])
print(array)
// add
array.append(0)
print(array)
array.append(contentsOf: [5,5,5])
print(array)
// filter
array = array.filter { import Foundation
var arr = AtomicArray([0,1,2,3,4,5])
for i in 0...1000 {
// Single actions
DispatchQueue.global(qos: .background).async {
usleep(useconds_t(Int.random(in: 100...10000)))
let num = i*i
arr.append(num)
print("arr.append(\(num)), background queue")
}
DispatchQueue.global(qos: .default).async {
usleep(useconds_t(Int.random(in: 100...10000)))
arr.append(arr.count)
print("arr.append(\(arr.count)), default queue")
}
// multy actions
DispatchQueue.global(qos: .utility).async {
arr.set { array -> [Int] in
var newArray = array
newArray.sort()
print("sort(), .utility queue")
return newArray
}
}
}
< 7 }
print(array)
// map
let strings = array.map { "\(let queue = DispatchQueue(label: "com.readerWriter", qos: .background, attributes: .concurrent)
var safeArray: [String] = []
subscript(index: Int) -> String {
get {
queue.sync {
return safeArray[index]
}
}
set(newValue) {
queue.async(flags: .barrier) { [weak self] in
self?.safeArray[index] = newValue
}
}
}
)" }
print(strings)
// insert
array.insert(99, at: 5)
print(array)
array.insert(contentsOf: [2, 2, 2], at: 0)
print(array)
// pop
_ = array.popLast()
print(array)
_ = array.popFirst()
print(array)
// remove
array.removeFirst()
print(array)
array.removeFirst(3)
print(array)
array.remove(at: 2)
print(array)
array.removeLast()
print(array)
array.removeLast(5)
print(array)
array.removeAll { class MyClass {
let lock = Lock()
var myArray: Array<Int> = []
func networkRequestWhatEver() {
lock.withLock {
array.append(someValue)
}
}
}
%2 == 0 }
print(array)
array = AtomicArray([1,2,3,4,5,6,7,8,9,0])
array.removeSubrange(0...2)
print(array)
array.replaceSubrange(0...2, with: [0,0,0])
print(array)
array.removeAll()
print(array)
array.set(array: [1,2,3,4,5,6,7,8,9,0])
print(array)
// subscript
print(array[0])
array[0] = 100
print(array)
print(array[1...4])
// operator functions
array = [1,2,3] + AtomicArray([4,5,6])
print(array)
array = AtomicArray([4,5,6]) + [1,2,3]
print(array)
array = AtomicArray([1,2,3]) + AtomicArray([4,5,6])
print(array)
Usage sample 2
使用示例2
objc_sync_enter(array)
defer {
objc_sync_exit(array)
}
回答by Sathish Kumar Gurunathan
Here is the answer for Swift 4,
这是 Swift 4 的答案,
private let syncQueue = DispatchQueue(label:"com.test.LockQueue")
func test(){
self.syncQueue.sync{
// thread safe code here
}
}
回答by swift lynx
Swift-Nio & Vapor Swift
Swift-Nio 和 Vapor Swift
For those of you using Swift-Nio (or Vapor Swift which is based on Swift-Nio), there's a built in solution for this problem:
对于那些使用 Swift-Nio(或基于 Swift-Nio 的 Vapor Swift)的人来说,有一个针对这个问题的内置解决方案:
public class ThreadSafeArray<Element> {
private var elements : [Element]
private let syncQueue = DispatchQueue(label: "Sync Queue",
qos: .default,
attributes: .concurrent,
autoreleaseFrequency: .inherit,
target: nil)
public init() {
elements = []
}
public init(_ newElements: [Element]) {
elements = newElements
}
//MARK: Non-mutating
public var first : Element? {
return syncQueue.sync {
elements.first
}
}
public var last : Element? {
return syncQueue.sync {
elements.last
}
}
public var count : Int {
return syncQueue.sync {
elements.count
}
}
public subscript(index: Int) -> Element {
get {
return syncQueue.sync {
elements[index]
}
}
set {
syncQueue.sync(flags: .barrier) {
elements[index] = newValue
}
}
}
public func reversed() -> [Element] {
return syncQueue.sync {
elements.reversed()
}
}
public func flatMap<T>(_ transform: (Element) throws -> T?) rethrows -> [T] {
return try syncQueue.sync {
try elements.flatMap(transform)
}
}
public func filter(_ isIncluded: (Element) -> Bool) -> [Element] {
return syncQueue.sync {
elements.filter(isIncluded)
}
}
//MARK: Mutating
public func append(_ element: Element) {
syncQueue.sync(flags: .barrier) {
elements.append(element)
}
}
public func append<S>(contentsOf newElements: S) where Element == S.Element, S : Sequence {
syncQueue.sync(flags: .barrier) {
elements.append(contentsOf: newElements)
}
}
public func remove(at index: Int) -> Element? {
var element : Element?
syncQueue.sync(flags: .barrier) {
if elements.startIndex ..< elements.endIndex ~= index {
element = elements.remove(at: index)
}
else {
element = nil
}
}
return element
}
}
extension ThreadSafeArray where Element : Equatable {
public func index(of element: Element) -> Int? {
return syncQueue.sync {
elements.index(of: element)
}
}
}
Note that you should use the same Lock
object when modifing the same Array
object (or Dictionary
, etc.).
请注意,Lock
修改同一Array
对象(或Dictionary
等)时应使用同一对象。
回答by lbsweek
firstly, objc_sync_enter not works
首先,objc_sync_enter 不起作用
##代码##reason objc_sync_enter / objc_sync_exit not working with DISPATCH_QUEUE_PRIORITY_LOW
原因objc_sync_enter / objc_sync_exit 不适用于 DISPATCH_QUEUE_PRIORITY_LOW
objc_sync_enter is an extremely low-level primitive, and isn't intended to be used directly. It's an implementation detail of the old @synchronized system in ObjC.
objc_sync_enter 是一个非常低级的原语,不打算直接使用。这是 ObjC 中旧的 @synchronized 系统的一个实现细节。
for swift, should use like this, just as @Kirsteins said, and I suggest sync instead of async:
对于 swift,应该像这样使用,正如@Kirsteins 所说,我建议同步而不是异步:
##代码##回答by user1046037
Approach:
方法:
Use DispatchQueue
to synchronise
使用DispatchQueue
同步
Refer:
参考:
http://basememara.com/creating-thread-safe-arrays-in-swift/
http://basememara.com/creating-thread-safe-arrays-in-swift/
Code:
代码:
Below is a crude implementation of a thread safe array, you can fine tune it.
下面是一个线程安全数组的粗略实现,您可以对其进行微调。
##代码##回答by amol-c
I think dispatch_barriers are worth looking into. Using gcd for synchronicity is more intuitive to me than using synchronize keyword to avoid state mutation from multiple threads.
我认为 dispatch_barriers 值得研究。对我来说,使用 gcd 进行同步比使用同步关键字来避免来自多个线程的状态突变更直观。
https://mikeash.com/pyblog/friday-qa-2011-10-14-whats-new-in-gcd.html
https://mikeash.com/pyblog/friday-qa-2011-10-14-whats-new-in-gcd.html