Swift 2:struct thread-safety

时间:2015-07-25 11:29:29

标签: ios macos swift grand-central-dispatch

在我的快速练习中,我编写了名为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定义为类(不是结构),则可以。

如果使用信号量而不是使用串行队列,则没关系。

我想知道结构和串行队列对不稳定的原因。

----更新

我得到了预期的结果。

  1. 类而不是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. 信号量而不是串行队列

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

3 个答案:

答案 0 :(得分:3)

此代码将捕获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:[]”。

  • 完成后,封闭内的testSet内的10个副本会尝试复制回到testSet以外的唯一内容。但是,只有一个赢家,例如,外部testSet的当前值是“1:[3]”。

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

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

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

案例2更复杂。实际上我还没弄清楚幕后发生了什么。我认为它更多的是关于swift编译器的实现细节,可能会改变不同的swift版本。看起来信号量是一种引用类型,因此'testSet'的所有副本都共享相同的信号量。我想complier决定在这种情况下进行一些优化,并将testSet s'__element的所有副本指向同一个数组。因此结果包含所有0 ..&lt; 20元素,但顺序是不可预测的。

答案 1 :(得分:0)

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

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

在信号量的情况下,没有关闭所以没有捕获所以自我就是自我。变异发生在原始的外部自我身上,而信号量确保每个人都在有序的线条中这样做。

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

答案 2 :(得分:0)

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