簡體   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