Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread Safety for a getter and setter in a singleton

Tags:

ios

swift

swift3

I have created an simple singleton in Swift 3:

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

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

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

Since I made the init() private , and also declared shared instance to be static let, I think the initializer is thread safe. But what about the getter and setter functions for myName, are they thread safe?

like image 524
Leem.fin Avatar asked Aug 16 '17 09:08

Leem.fin


People also ask

How can you make a singleton class thread-safe?

Thread Safe Singleton in JavaCreate the private constructor to avoid any new object creation with new operator. Declare a private static instance of the same class. Provide a public static method that will return the singleton class instance variable.

Is a singleton class thread-safe?

Is singleton thread safe? A singleton class itself is not thread safe. Multiple threads can access the singleton same time and create multiple objects, violating the singleton concept. The singleton may also return a reference to a partially initialized object.

Is eager initialization thread-safe?

It is thread-safe even if we remove final from public static final Singleton instance = new Singleton (); .

How do I create a thread-safe singleton class in Swift?

You can create a thread safe singleton using DispatchQueue. We will create a serial queue and add a sync method to set and get value. Below is the code you can achieve thread safe singleton class. It will write an another blog where we can improve the performance using dispatch barrier.


2 Answers

A slightly different way to do it (and this is from an Xcode 9 Playground) is to use a concurrent queue rather than a serial queue.

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
            }
        }
    }
}
  • Using a concurrent queue means that multiple reads from multiple threads aren't blocking each other. Since there is no mutation on getting, the value can be read concurrently, because...
  • We are setting the new values using a .barrier async dispatch. The block can be performed asynchronously because there is no need for the caller to wait for the value to be set. The block will not be run until all the other blocks in the concurrent queue ahead of it have completed. So, existing pending reads will not be affected while this setter is waiting to run. The barrier means that when it starts running, no other blocks will run. Effectively, turning the queue into a serial queue for the duration of the setter. No further reads can be made until this block completes. When the block has completed the new value has been set, any getters added after this setter can now run concurrently.
like image 71
Abizern Avatar answered Sep 24 '22 03:09

Abizern


You are correct that those getters that you've written are not thread safe. In Swift, the simplest (read safest) way to achieve this at the moment is using Grand Central Dispatch queues as a locking mechanism. The simplest (and easiest to reason about) way to achieve this is with a basic serial queue.

class MySingleton {

    static let shared = MySingleton()

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

    private var _name: String
    var name: String {
        get {
            return lockQueue.sync {
                return _name
            }
        }

        set {
            lockQueue.sync {
                _name = newValue
            }
        }
    }

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

Using a serial dispatch queue will guarantee first in, first out execution as well as achieving a "lock" on the data. That is, the data cannot be read while it is being changed. In this approach, we use sync to execute the actual reads and writes of data, which means the caller will always be forced to wait its turn, similar to other locking primitives.

Note: This isn't the most performant approach, but it is simple to read and understand. It is a good general purpose solution to avoid race conditions but isn't meant to provide synchronization for parallel algorithm development.

Sources: 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"?

like image 40
allenh Avatar answered Sep 24 '22 03:09

allenh