简体   繁体   English

Swift 2:结构线程安全

[英]Swift 2: struct thread-safety

In my swift practice, I wrote simple struct named OrderedSet .在我的快速实践中,我编写了一个名为OrderedSet简单结构。

I tried OrderedSet to be a thread-safe with GCD serial queue.我尝试将OrderedSet设为具有 GCD 串行队列的线程安全。

But it's not working.但它不起作用。 The test result is unstable.测试结果不稳定。 I expected something like:我期待这样的事情:

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

but received something like like但收到类似的东西

2:[3, 19]

here is playground code:这是游乐场代码:

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()

I've checked below:我在下面检查过:

It's OK if defined OrderdSet as a class (not struct).如果将OrderdSet定义为类(而不是结构体)就可以了。

It's OK if using semaphore instead of using serial queue.如果使用信号量而不是使用串行队列就可以了。

I would like to know the reason why the pair of struct and serial queue is unstable.我想知道这对 struct 和 serial 队列不稳定的原因。

---- updated - - 更新

I got the expected result with these.我用这些得到了预期的结果。

  1. class instead of struct类而不是结构

    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. semaphore instead of serial queue信号量而不是串行队列

    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. serial queue with OrderdSet itself.带有 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()

This code will capture current value of testSet :此代码将捕获testSet当前值

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

After the execution of the closure, the value of the inside testSet will be copied to the outside testSet variable.闭包执行后,内部testSet的值会被复制到外部testSet变量中。

Imagine a concurrent world:想象一个并发世界:

  • 10 closures are running simultaneously, capturing the initial value of the outside testSet , which is "0:[]". 10 个闭包同时运行,捕获外部testSet的初始值,即“0:[]”。

  • Once finished, 10 copy of testSet s inside closures try to copy back to the only outside testSet .完成后,闭包内的 10 个testSet副本尝试复制回唯一的外部testSet However, there is only one winner, say, current value of the outside testSet is "1:[3]".但是,只有一个获胜者,例如,外部testSet当前值为“1:[3]”。

  • Yet another round start, capturing current value of the outside testSet which is "1:[3]", appending i , and copying back, yielding the weird result, say, "2:[3, 19]"又一轮开始,捕获外部testSet当前值,即“1:[3]”,附加i ,然后复制回来,产生奇怪的结果,例如“2:[3, 19]”

In your updated case 1, changing OrderedSet to class, things are pretty straight forward, testSet is captured by reference, and all the threads are sharing the same object.在您更新的案例 1 中,将OrderedSet更改为类,事情非常简单, testSet是通过引用捕获的,并且所有线程都共享同一个对象。

In your updated case 3, by using serial queue, I guess every appending and copying back operation is serial, so you yield a perfect ordered set.在您更新的案例 3 中,通过使用串行队列,我猜每个追加和复制操作都是串行的,因此您会产生一个完美的有序集。

Case 2 is more complicated.情况2比较复杂。 Actually I haven't figure out what's going on under the hood.其实我还没有弄清楚引擎盖下发生了什么。 And I think it's more about a implementation detail of the swift compiler and may change over different swift versions.而且我认为这更多是关于 swift 编译器的实现细节,并且可能会随着不同的 swift 版本而改变。 It seems like semaphore is a reference type, thus all the copy of the 'testSet's are sharing the same semaphore.信号量似乎是一种引用类型,因此“testSet”的所有副本都共享相同的信号量。 I guess complier decide to do some optimization in this case and make all the copy of the testSet s' __element point to the same array.我猜编译器决定在这种情况下做一些优化,并使testSet s' __element所有副本指向同一个数组。 So the result contains all the 0..<20 elements but the order is unpredictable.所以结果包含所有 0..<20 个元素,但顺序是不可预测的。

I think what's happening when dispatch_sync is used inside the struct is that self is implicitly captured by the closure as an inout parameter.我认为在结构中使用dispatch_sync时发生的事情是self被闭包作为inout参数隐式捕获。

That means that a copy is modified, which then replaces the outer self on return.这意味着修改了一个副本,然后在返回时替换了外部的self So there's multiple concurrent copies of self being mutated, then clobbering the original.所以有多个并发的 self 副本被变异,然后破坏了原始副本。

In the semaphores case there is no closure so there's no capture so self is self is self.在信号量的情况下,没有闭包,所以没有捕获,所以自我就是自我就是自我。 The mutating happens on the original outer self, and the semaphores ensure that everyone does so in an orderly line.变异发生在原始的外在自我上,信号量确保每个人都按照有序的方式这样做。

I've run into the same thing when using using a closure wrapper around pthread mutexes for getters and setters inside a struct.当在结构内的 getter 和 setter 中使用 pthread 互斥体周围的闭包包装器时,我遇到了同样的事情。 Even though the closure parameter is non escaping, the struct (ie self ) still seems to be treated as an inout , so messy things happen.即使闭包参数是非转义的,结构(即self )似乎仍然被视为inout ,所以会发生混乱的事情。

Value types in iOS are stored in stack and each thread has its own stack. iOS 中的值类型存储在堆栈中,每个线程都有自己的堆栈。 So, in the struct, values will be copied when you access from a different stack.因此,在结构中,当您从不同的堆栈访问时,值将被复制。 Thanks.谢谢。

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

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