2017-08-16 138 views
4

我在斯威夫特3中創建一個簡單單的單身getter和setter:線程安全的

class MySingleton { 
    private var myName: String 
    private init() {} 
    static let shared = MySingleton() 

    func setName(_ name: String) { 
     myName = name 
    } 

    func getName() -> String { 
     return myName 
    } 
} 

因爲我做了init()私有的,也宣告shared實例是static let,我想初始化是線程安全的。但是myName的getter和setter函數又是如何線程安全的?

+0

我認爲它不是線程安全的,即使它是單例或不是 –

+0

如何使線程安全? –

+0

添加串行調度隊列作爲該類的成員,並對其執行get/set操作 – Alexander

回答

6

你說得對,你寫的那些getter不是線程安全的。在Swift中,目前最簡單(最安全)的方法是使用Grand Central Dispatch隊列作爲鎖定機制。最簡單(也是最容易推理的)方法是使用基本的串行隊列。

class MySingleton { 

    static let shared = MySingleton() 

    // Serial dispatch queue 
    private let lockQueue = DispatchQueue(label: "MySingleton.lockQueue") 

    private var _myName: String 
    var myName: String { 
     get { 
      var result: String! 

      lockQueue.sync { 
       result = self._myName 
      } 

      return result 
     } 

     set { 
      lockQueue.sync { 
       self._myName = newValue 
      } 
     } 
    } 

    private init() { 
     _myName = "initial name" 
    } 
} 

使用串行調度隊列將保證先進先出執行以及實現對數據的「鎖定」。也就是說,數據在更改時無法讀取。在這種方法中,我們使用sync來執行數據的實際讀取和寫入,這意味着調用者將總是被迫等待其輪到,類似於其他鎖定基元。

注意:這不是most performant的方法,但它很容易閱讀和理解。這是一個很好的通用目的解決方案,以避免競爭條件,但並不意味着爲並行算法開發提供同步。

來源: https://mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.html What is the Swift equivalent to Objective-C's "@synchronized"?

+0

值得一提的是,沒有什麼比「快速實現這一點的最佳方式」存在。線程安全或併發編程與使用的編程語言無關,並且與單例構造完全無關。第一個要回答的問題不應該是如何,而是爲什麼你需要同步你的數據。如果沒有優勢,最好避免任何並行或併發。你的回答幾乎是正確的,問題很模糊...... – user3441734

+0

@ user3441734由於GCD(或其他技術)在所有環境中都不可用,我會爭辯說存在連接。這個問題使用單例作爲前提,但最終這個問題與單身人士無關。說「最好避免任何並行或併發」是非常糟糕的建議。事實上,爲了避免iOS中的併發比簡單地接受和理解它更具挑戰性。 –

+0

我寫道:「如果沒有一些優勢,最好避免......」我是GCD的一大樂趣,我幾乎在所有項目中都使用它。 – user3441734

5

稍微不同的方式來做到這一點(這是從一個Xcode 9遊樂場)是使用併發隊列,而不是一個串行隊列。

final class MySingleton { 
    static let shared = MySingleton() 

    private let nameQueue = DispatchQueue(label: "name.accessor", qos: .default, attributes: .concurrent) 
    private var _name = "Initial name" 

    private init() {} 

    var name: String { 
     get { 
      var name = "" 
      nameQueue.sync { 
       name = _name 
      } 

      return name 
     } 
     set { 
      nameQueue.async(flags: .barrier) { 
       self._name = newValue 
      } 
     } 
    } 
} 
  • 使用併發隊列意味着多個讀取來自多個線程未阻止彼此。由於在獲取時沒有突變,因此可以同時讀取該值,因爲...
  • 我們正在使用.barrier異步分派來設置新值。該塊可以異步執行,因爲調用者不需要等待值的設置。該塊將不會運行,直到其前面的併發隊列中的所有其他塊完成。所以,當這個setter等待運行時,現有的掛起讀操作不會受到影響。障礙意味着當它開始運行時,不會有其他塊運行。有效地,在setter的持續時間內將隊列變成一個串行隊列。在此模塊完成之前不能進一步讀取。當程序塊完成後,新的值已經被設置,在這個setter之後添加的任何獲得者現在可以同時運行。
+2

我非常喜歡這種變化。據我瞭解,就數據一致性而言,它將是「正確的」。唯一不同的是,對於一個初學者來說,對setter的調用語義會有些違反直覺,他們可能會在「獲取鎖定」時期待延遲。基本上,在執行器執行後「有效設置」和「實際設置」,但這並不重要。 –

+0

@AllenHumphreys,使用「你的」(連續)或「Abizern」(障礙)的方法取決於很多因素。在併發隊列上使用屏障並不是違反直覺的,它是不同的。它有一些優勢,也有一些劣勢。有時他們兩人都可能失敗。 – user3441734