Thread safe variable access, functional way. Part I.
Correctness of the threaded code has always been a concern. The common pattern is guarding some shared state with a serial dispatch queue:
func setCounter(value: Int) {
dispatch_async(syncQueue) {
self.counter = value
}
}
func getCounter() -> Int {
var result = 0
dispatch_sync(syncQueue) {
result = self.counter
}
return result
}
This code has a big drawback - nothing prevents developer from accessing counter
from outside the syncQueue
.
We can easily fix that by wrapping the variable in a container:
class AccessibleOnQueue<T> {
init(_ value: T) {
self.value = value
}
private var value: T
}
But how do we access that value? We don’t. Instead we provide a way to apply a function to the contained value:
extension AccessibleOnQueue {
func accessor<U>(f: (inout T) -> U)
-> AccessibleOnQueue<() -> U> {
return AccessibleOnQueue<() -> U>({
return f(&self.value)
})
}
}
Note the type of accessor
, it returns a function ‘lifted’ into the container type. This function implements some algorithm over a protected variable.
But there still is no way of calling this construction. Let’s introduce one:
func accessAsync<T>(accessor: AccessibleOnQueue<() -> T>,
# queue: dispatch_queue_t) {
dispatch_async(queue) {
accessor.value()
}
}
func accessSync<T>(accessor: AccessibleOnQueue<() -> T>,
# queue: dispatch_queue_t) -> T {
var result: T?
dispatch_sync(queue) {
result = accessor.value()
}
return result!
}
Returning to code in the beginning, this is how it looks now:
func setCounter(value: Int) {
let setter = self.counter.accessor() {
(inout counter: Int) -> () in
counter = value
}
accessAsync(setter, queue: syncQueue)
}
func getCounter() -> Int {
let getter = self.counter.accessor() {
(inout counter: Int) -> Int in
return counter
}
return accessSync(getter, queue: syncQueue)
}
Not too much longer, but much safer.
That’s the end of part I, in the next post I will demonstrate how to use this construction to compose operations on protected data and why is it called Applicative Functor.