繁体   English   中英

Swift 2:结构线程安全

[英]Swift 2: struct thread-safety

在我的快速实践中,我编写了一个名为OrderedSet简单结构。

我尝试将OrderedSet设为具有 GCD 串行队列的线程安全。

但它不起作用。 测试结果不稳定。 我期待这样的事情:

20:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

但收到类似的东西

2:[3, 19]

这是游乐场代码:

import Foundation
import XCPlayground

struct OrderedSet<T: Equatable> {
    mutating func append(e: T) {
        dispatch_sync(q) {
            if !self.__elements.contains(e) {
                self.__elements.append(e)
            }
        }
    }
    var elements: [T] {
        var elements: [T] = []
        dispatch_sync(q) {
            elements = self.__elements
        }
        return elements
    }
    var count: Int {
        var ret = 0
        dispatch_sync(q) {
            ret = self.__elements.count
        }
        return ret
    }
    private var __elements: [T] = []
    private let q = dispatch_queue_create("OrderedSet.private.serial.queue", DISPATCH_QUEUE_SERIAL)
}
extension OrderedSet: CustomStringConvertible {
    var description: String {
        var text = ""
        dispatch_sync(q) {
            text = "\(self.__elements.count):\(self.__elements)"
        }
        return text
    }
}

// Test code
let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let group = dispatch_group_create()

var testSet = OrderedSet<Int>()
for i in 0..<20 {
    dispatch_group_async(group, globalQueue) {
        testSet.append(i)
    }
}
dispatch_group_notify(group, globalQueue) {
    print("\(testSet)") // unstable result
}

XCPSetExecutionShouldContinueIndefinitely()

我在下面检查过:

如果将OrderdSet定义为类(而不是结构体)就可以了。

如果使用信号量而不是使用串行队列就可以了。

我想知道这对 struct 和 serial 队列不稳定的原因。

- - 更新

我用这些得到了预期的结果。

  1. 类而不是结构

    import Foundation import XCPlayground class OrderedSet<T: Equatable> { func append(e: T) { dispatch_sync(q) { if !self.__elements.contains(e) { self.__elements.append(e) } } } var elements: [T] { var elements: [T] = [] dispatch_sync(q) { elements = self.__elements } return elements } var count: Int { var ret = 0 dispatch_sync(q) { ret = self.__elements.count } return ret } private var __elements: [T] = [] private let q = dispatch_queue_create("OrderedSet.private.serial.queue", DISPATCH_QUEUE_SERIAL) } extension OrderedSet: CustomStringConvertible { var description: String { var text = "" dispatch_sync(q) { text = "\\(self.__elements.count):\\(self.__elements)" } return text } } // Test code let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) let group = dispatch_group_create() var testSet = OrderedSet<Int>() for i in 0..<20 { dispatch_group_async(group, globalQueue) { testSet.append(i) } } dispatch_group_notify(group, globalQueue) { print("\\(testSet)") // It's OK } XCPSetExecutionShouldContinueIndefinitely()
  2. 信号量而不是串行队列

    import Foundation import XCPlayground struct OrderedSet<T: Equatable> { mutating func append(e: T) { dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER) if !self.__elements.contains(e) { self.__elements.append(e) } dispatch_semaphore_signal(s) } var elements: [T] { var elements: [T] = [] dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER) elements = self.__elements dispatch_semaphore_signal(s) return elements } var count: Int { var ret = 0 dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER) ret = self.__elements.count dispatch_semaphore_signal(s) return ret } private var __elements: [T] = [] private let s = dispatch_semaphore_create(1) } extension OrderedSet: CustomStringConvertible { var description: String { var text = "" dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER) text = "\\(self.__elements.count):\\(self.__elements)" dispatch_semaphore_signal(s) return text } } // Test code let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) let group = dispatch_group_create() var testSet = OrderedSet<Int>() for i in 0..<20 { dispatch_group_async(group, globalQueue) { testSet.append(i) } } dispatch_group_notify(group, globalQueue) { print("\\(testSet)") // It's OK } XCPSetExecutionShouldContinueIndefinitely()
  3. 带有 OrderdSet 本身的串行队列。

     import Foundation import XCPlayground struct OrderedSet<T: Equatable> { mutating func append(e: T) { if !self.__elements.contains(e) { self.__elements.append(e) } } var elements: [T] { return self.__elements } var count: Int { return self.__elements.count } private var __elements: [T] = [] } extension OrderedSet: CustomStringConvertible { var description: String { return "\\(self.__elements.count):\\(self.__elements)" } } // Test code let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) let serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL) let group = dispatch_group_create() var testSet = OrderedSet<Int>() for i in 0..<20 { dispatch_group_async(group, globalQueue) { dispatch_sync(serialQueue) { testSet.append(i) } } } dispatch_group_notify(group, serialQueue) { print("\\(testSet)") // It's OK } XCPSetExecutionShouldContinueIndefinitely()

此代码将捕获testSet当前值

dispatch_group_async(group, globalQueue) {
    testSet.append(i) // `testSet` inside the closure will be a copy of the `testSet` variable outside 
}

闭包执行后,内部testSet的值会被复制到外部testSet变量中。

想象一个并发世界:

  • 10 个闭包同时运行,捕获外部testSet的初始值,即“0:[]”。

  • 完成后,闭包内的 10 个testSet副本尝试复制回唯一的外部testSet 但是,只有一个获胜者,例如,外部testSet当前值为“1:[3]”。

  • 又一轮开始,捕获外部testSet当前值,即“1:[3]”,附加i ,然后复制回来,产生奇怪的结果,例如“2:[3, 19]”

在您更新的案例 1 中,将OrderedSet更改为类,事情非常简单, testSet是通过引用捕获的,并且所有线程都共享同一个对象。

在您更新的案例 3 中,通过使用串行队列,我猜每个追加和复制操作都是串行的,因此您会产生一个完美的有序集。

情况2比较复杂。 其实我还没有弄清楚引擎盖下发生了什么。 而且我认为这更多是关于 swift 编译器的实现细节,并且可能会随着不同的 swift 版本而改变。 信号量似乎是一种引用类型,因此“testSet”的所有副本都共享相同的信号量。 我猜编译器决定在这种情况下做一些优化,并使testSet s' __element所有副本指向同一个数组。 所以结果包含所有 0..<20 个元素,但顺序是不可预测的。

我认为在结构中使用dispatch_sync时发生的事情是self被闭包作为inout参数隐式捕获。

这意味着修改了一个副本,然后在返回时替换了外部的self 所以有多个并发的 self 副本被变异,然后破坏了原始副本。

在信号量的情况下,没有闭包,所以没有捕获,所以自我就是自我就是自我。 变异发生在原始的外在自我上,信号量确保每个人都按照有序的方式这样做。

当在结构内的 getter 和 setter 中使用 pthread 互斥体周围的闭包包装器时,我遇到了同样的事情。 即使闭包参数是非转义的,结构(即self )似乎仍然被视为inout ,所以会发生混乱的事情。

iOS 中的值类型存储在堆栈中,每个线程都有自己的堆栈。 因此,在结构中,当您从不同的堆栈访问时,值将被复制。 谢谢。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM