Created
May 9, 2016 17:56
-
-
Save algal/852531d09aabf77da14e9012d92d3ab3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: Building thread-safe value types with GCD | |
/** | |
This playground walks through an example trying to create a thread-safe counter type, | |
and noting the difficult of doing this while maintaining value semantics. | |
An incrementer is not interesting in itself, but it is the simplest example illustrating problems which also appear for more useful types, like dictionaries or arrays. | |
//: Reference-type solution | |
This is a fairly well-understood problem if you are trying to create a reference type. The old approach is to use `synchronize` and locks in order to exclude multiple threads from running the counter's setters simultaneously. The modern approach is to create a dedicated private concurrent dispatch queue, and to configure the dictionary's setter to perform all the mutating work inside a _barrier block_. A barrier block is a block (i.e., closure), that will only run exclusively in its queue, even if it is a concurrent queue which normally allows multiple blocks to run simultaneously. | |
Because the dispatch queue is a queue, it is okay to dispatch the barrier block _asynchronously_. This is because, even if the calling thread does not wait for the write to complete, it is still the case that no future read or write operations will execute until the write block completes. | |
Here is the code for this approach: | |
*/ | |
import Foundation | |
// This class-based solution works | |
class IncrementerR { | |
let queue = dispatch_queue_create("access queue", DISPATCH_QUEUE_CONCURRENT) | |
private var _v:Int = 0 | |
func inc() { | |
dispatch_barrier_async(self.queue) { | |
self._v = self._v + 1 | |
} | |
} | |
var value:Int { | |
var retValue:Int? | |
dispatch_sync(self.queue) { | |
retValue = self._v | |
} | |
return retValue! | |
} | |
} | |
/** | |
As one can see, the type behaves as expected, as `i` increments: | |
*/ | |
var i = IncrementerR() | |
i.value | |
i.inc() | |
i.value // => 1, as expected. | |
//: Value-type based approach | |
/** | |
However, the example above creates a type with references semantics. | |
What if we want to create a Dictionary-like type with value semantics? | |
The obvious thing to do is to change the type from a `class` to a `struct`, and to mark the mutating method `mutating`, like so: | |
*/ | |
// This struct-based solution does not work | |
struct IncrementerS1 { | |
private let queue = dispatch_queue_create("access queue", DISPATCH_QUEUE_CONCURRENT) | |
private var _v:Int = 0 | |
mutating func inc() { | |
// sync or async | |
dispatch_barrier_async(self.queue) { | |
self._v = self._v + 1 | |
} | |
} | |
var value:Int { | |
var retValue:Int? | |
dispatch_sync(self.queue) { | |
retValue = self._v | |
} | |
return retValue! | |
} | |
} | |
/// However, the type no longer works, as `j` no longer increments. | |
var j = IncrementerS1() | |
j.value | |
j.inc() | |
j.value // => 0 . why? | |
/** | |
Why does the increment operation no longer take effect? | |
You might expect the `dispatch_barrier_async` call to ensure that all reads from `value` after the call to `inc()` would see the effect of `self._v = self._v +1`. But instead, the read is returning 0 as if nothing happened. | |
This is because `IncrementerS1` is a value type. | |
Since it is a value type, the closure that we are passing to `dispatch_barrier_async` is capturing a _copy of the value_ bound to `self`, not a reference to an instance that will be remembered and modified later when the closure runs. | |
So while the line `self._v = self._v +1` does change what value is represented by `self` at the end of that closure, there is nothing which assigns that value to the external variable `j`. That external variable `j` will instead hold a copy of `self` as it was when `inc()` completed. | |
So how do we fix that? One way is to ensure that the change takes place before `inc()` exits, by switch to `dispatch_barrier_sync`. | |
*/ | |
struct IncrementerS2 { | |
private let queue = dispatch_queue_create("access queue", DISPATCH_QUEUE_CONCURRENT) | |
private var _v:Int = 0 | |
mutating func inc() { | |
// sync or async | |
dispatch_barrier_sync(self.queue) { | |
self._v = self._v + 1 | |
} | |
} | |
var value:Int { | |
var retValue:Int? | |
dispatch_sync(self.queue) { | |
retValue = self._v | |
} | |
return retValue! | |
} | |
} | |
/** | |
Having made this change, incrementing works again. Our thread-safe incrementer now uses value semantics and works. | |
*/ | |
var k = IncrementerS2() | |
k.value | |
k.inc() | |
k.value // => 0 . why? | |
/** | |
However, there is still one way in which it does not fully implement correct value semantics. This is that its copy semantics are not like value-like. | |
This is because the private dispatch queue itself is a reference type. So if you copied values of `IncrementerS2`, those distinct values would be sharing the same dispatch queue unncessarily. | |
We can see this by peeking at the internal queue objects within a copied value | |
*/ | |
var m1 = IncrementerS2() | |
var m2 = m1 // we want m2 to be distinct value | |
let isQueueShared:Bool = (m1.queue === m2.queue) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment