Use queue and semaphore for concurrency and property wrapper? - swift

I'm trying to create a thread-safe property wrapper. I could only think of GCD queues and semaphores as being the most Swifty and reliable way. Are semaphore's just more performant (if that's true), or is there another reason to use one over the other for concurrency?
Below are two variants of atomic property wrappers:
#propertyWrapper
struct Atomic<Value> {
private var value: Value
private let queue = DispatchQueue(label: "Atomic serial queue")
var wrappedValue: Value {
get { queue.sync { value } }
set { queue.sync { value = newValue } }
}
init(wrappedValue value: Value) {
self.value = value
}
}
#propertyWrapper
struct Atomic2<Value> {
private var value: Value
private var semaphore = DispatchSemaphore(value: 1)
var wrappedValue: Value {
get {
semaphore.wait()
let temp = value
semaphore.signal()
return temp
}
set {
semaphore.wait()
value = newValue
semaphore.signal()
}
}
init(wrappedValue value: Value) {
self.value = value
}
}
struct MyStruct {
#Atomic var counter = 0
#Atomic2 var counter2 = 0
}
func test() {
var myStruct = MyStruct()
DispatchQueue.concurrentPerform(iterations: 1000) {
myStruct.counter += $0
myStruct.counter2 += $0
}
}
How can they be properly tested and measured to see the difference between the two implementations and if they even work?

FWIW, another option is reader-writer pattern with concurrent queue, where reads are done synchronously, but are allowed to run concurrently with respect to other reads, but writes are done asynchronously, but with a barrier (i.e. not concurrently with respect to any other reads or writes):
#propertyWrapper
class Atomic<Value> {
private var value: Value
private let queue = DispatchQueue(label: "com.domain.app.atomic", attributes: .concurrent)
var wrappedValue: Value {
get { queue.sync { value } }
set { queue.async(flags: .barrier) { self.value = newValue } }
}
init(wrappedValue value: Value) {
self.value = value
}
}
Yet another is NSLock:
#propertyWrapper
class Atomic<Value> {
private var value: Value
private var lock = NSLock()
var wrappedValue: Value {
get { lock.synchronized { value } }
set { lock.synchronized { value = newValue } }
}
init(wrappedValue value: Value) {
self.value = value
}
}
where
extension NSLocking {
func synchronized<T>(block: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try block()
}
}
Or you can use unfair locks:
#propertyWrapper
class SynchronizedUnfairLock<Value> {
private var value: Value
private var lock = UnfairLock()
var wrappedValue: Value {
get { lock.synchronized { value } }
set { lock.synchronized { value = newValue } }
}
init(wrappedValue value: Value) {
self.value = value
}
}
Where
// One should not use `os_unfair_lock` directly in Swift (because Swift
// can move `struct` types), so we'll wrap it in a `UnsafeMutablePointer`.
// See https://github.com/apple/swift/blob/88b093e9d77d6201935a2c2fb13f27d961836777/stdlib/public/Darwin/Foundation/Publishers%2BLocking.swift#L18
// for stdlib example of this pattern.
final class UnfairLock: NSLocking {
private let unfairLock: UnsafeMutablePointer<os_unfair_lock> = {
let pointer = UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1)
pointer.initialize(to: os_unfair_lock())
return pointer
}()
deinit {
unfairLock.deinitialize(count: 1)
unfairLock.deallocate()
}
func lock() {
os_unfair_lock_lock(unfairLock)
}
func tryLock() -> Bool {
os_unfair_lock_trylock(unfairLock)
}
func unlock() {
os_unfair_lock_unlock(unfairLock)
}
}
We should recognize that while these, and yours, offers atomicity, you have to be careful because, depending upon how you use it, it may not be thread-safe.
Consider this simple experiment, where we increment an integer a million times:
func threadSafetyExperiment() {
#Atomic var foo = 0
DispatchQueue.global().async {
DispatchQueue.concurrentPerform(iterations: 10_000_000) { _ in
foo += 1
}
print(foo)
}
}
You’d expect foo to be equal to 10,000,000, but it won’t be. That is because the whole interaction of “retrieve the value and increment it and save it” needs to be wrapped in a single synchronization mechanism.
But you can add an atomic increment method:
extension Atomic where Value: Numeric {
mutating func increment(by increment: Value) {
lock.synchronized { value += increment }
}
}
And then this works fine:
func threadSafetyExperiment() {
#Atomic var foo = 0
DispatchQueue.global().async {
DispatchQueue.concurrentPerform(iterations: iterations) { _ in
_foo.increment(by: 1)
}
print(foo)
}
}
How can they be properly tested and measured to see the difference between the two implementations and if they even work?
A few thoughts:
I’d suggest doing far more than 1,000 iterations. You want to do enough iterations that the results are measured in seconds, not milliseconds. I used ten million iterations in my example.
The unit testing framework is ideal at both testing for correctness as well as measuring performance using the measure method (which repeats the performance test 10 times for each unit test and the results will be captured by the unit test reports):
So, create a project with a unit test target (or add a unit test target to existing project if you want) and then create unit tests, and execute them with command+u.
If you edit the scheme for your target, you can choose to randomize the order of your tests, to make sure the order in which they execute doesn’t affect the performance:
I would also make the test target use a release build to make sure you’re testing an optimized build.
Needless to say, while I am stress testing the locks by running 10m iterations, incrementing by one for each iteration, that is horribly inefficient. There simply is not enough work on each thread to justify the overhead of the thread handling. One would generally stride through the data set and do more iterations per thread, and reducing the number of synchronizations.
The practical implication of this, is that in well designed parallel algorithm, where you are doing enough work to justify the multiple threads, you are reducing the number of synchronizations that are taking place. Thus, the minor variances in the different synchronization techniques are unobservable. If the synchronization mechanism is having an observable performance difference, this probably suggests a deeper problem in the parallelization algorithm. Focus on reducing synchronizations, not making synchronizations faster.

Related

Task #Sendable operation

Writing a simple code:
class App {
private var value = 0
func start() async throws {
await withTaskGroup(of: Void.self) { group in
for _ in 1...100 {
group.addTask(operation: self.increment) // 1
group.addTask {
await self.increment() // 2
}
group.addTask {
self.value += 1 // 3
}
}
}
}
#Sendable private func increment() async {
self.value += 1 // 4
}
}
I got compile time warnings at lines 2, 3:
Capture of 'self' with non-sendable type 'App' in a #Sendable closure
However with enabled Thread Sanitizer, and removed lines 2 and 3, I got ThreadSanitizer runtime warning at line 4:
Swift access race in (1) suspend resume partial function for asyncTest.App.increment#Sendable () async -> () at 0x106003c80
So I have questions:
What is the difference between using these 3 .addTask ways?
What does #Sendable attribute does?
How can I make increment() function thread safe (data race free)?
For illustration of how to achieve thread-safety, consider:
class Counter {
private var value = 0
func incrementManyTimes() async {
await withTaskGroup(of: Void.self) { group in
for _ in 1...1_000_000 {
group.addTask {
self.increment() // no `await` as this is not `async` method
}
}
await group.waitForAll()
if value != 1_000_000 {
print("not thread-safe apparently; value =", value) // not thread-safe apparently; value = 994098
} else {
print("ok")
}
}
}
private func increment() { // note, this isn't `async` (as there is no `await` suspension point in here)
value += 1
}
}
That illustrates that it is not thread-safe, validating the warning from TSAN. (Note, I bumped the iteration count to make it easier to manifest the symptoms of non-thread-safe code.)
So, how would you make it thread-safe? Use an actor:
actor Counter {
private var value = 0
func incrementManyTimes() async {
await withTaskGroup(of: Void.self) { group in
for _ in 1...1_000_000 {
group.addTask {
await self.increment()
}
}
await group.waitForAll()
if value != 1_000_000 {
print("not thread-safe apparently; value =", value)
} else {
print("ok") // ok
}
}
}
private func increment() { // note, this still isn't `async`
value += 1
}
}
If you really want to use a class, add your own old-school synchronization. Here I am using a lock, but you could use a serial GCD queue, or whatever you want.
class Counter {
private var value = 0
let lock = NSLock()
func incrementManyTimes() async {
await withTaskGroup(of: Void.self) { group in
for _ in 1...1_000_000 {
group.addTask {
self.increment()
}
}
await group.waitForAll()
if value != 1_000_000 {
print("not thread-safe apparently; value =", value)
} else {
print("ok") // ok
}
}
}
private func increment() {
lock.synchronize {
value += 1
}
}
}
extension NSLocking {
func synchronize<T>(block: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try block()
}
}
Or, if you want to make Counter a Sendable type, too, confident that you are properly doing the synchronization yourself, like above, you can declare it as final and declare that it is Sendable, admittedly #unchecked by the compiler:
final class Counter: #unchecked Sendable {
private var value = 0
let lock = NSLock()
func incrementManyTimes() async {
// same as above
}
private func increment() {
lock.synchronize {
value += 1
}
}
}
But because the compiler cannot possibly reason about the actual “sendability” itself, you have to designate this as a #unchecked Sendable to let it know that you personally have verified it is really Sendable.
But actor is the preferred mechanism for ensuring thread-safety, eliminating the need for this custom synchronization logic.
For more information, see WWDC 2021 video Protect mutable state with Swift actors or 2022’s Eliminate data races using Swift Concurrency.
See Sendable documentation for a discussion of what the #Sendable attribute does.
BTW, I suspect you know this, but for the sake of future readers, while increment is fine for illustrative purposes, it is not a good candidate for parallelism. This parallelized rendition is actually slower than a simple, single-threaded solution. To achieve performance gains of parallelism, you need to have enough work on each thread to justify the modest overhead that parallelism entails.
Also, when testing parallelism, be wary of using the simulator, which significantly/artificially constrains the cooperative thread pool used by Swift concurrency. Test on macOS target, or a physical device. Neither the simulator nor a playground is a good testbed for this sort of parallelism exercise.

PropertyWrapper subscript is not called. WHY?

I am implementing my own AtomicDictionary property wrapper as follows:
#propertyWrapper
public class AtomicDictionary<Key: Hashable, Value>: CustomDebugStringConvertible {
public var wrappedValue = [Key: Value]()
private let queue = DispatchQueue(label: "atomicDictionary.\(UUID().uuidString)",
attributes: .concurrent)
public init() {}
public subscript(key: Key) -> Value? {
get {
queue.sync {
wrappedValue[key]
}
}
set {
queue.async(flags: .barrier) { [weak self] in
self?.wrappedValue[key] = newValue
}
}
}
public var debugDescription: String {
return wrappedValue.debugDescription
}
}
now, when I use it as follows:
class ViewController: UIViewController {
#AtomicDictionary var a: [String: Int]
override func viewDidLoad() {
super.viewDidLoad()
self.a["key"] = 5
}
}
The subscript function of the AtomicDicationary is not called!!
Does anybody have any explanation as to why that is?
Property wrappers merely provide an interface for the basic accessor methods, but that’s it. It’s not going to intercept subscripts or other methods.
The original property wrapper proposal SE-0258 shows us what is going on behind the scenes. It contemplates a hypothetical property wrapper, Lazy, in which:
The property declaration
#Lazy var foo = 1738
translates to:
private var _foo: Lazy<Int> = Lazy<Int>(wrappedValue: 1738)
var foo: Int {
get { return _foo.wrappedValue }
set { _foo.wrappedValue = newValue }
}
Note that foo is just an Int computed property. The _foo is the Lazy<Int>.
So, in your a["key"] = 5 example, it will not use your property wrapper’s subscript operator. It will get the value associated with a, use the dictionary’s own subscript operator to update that value (not the property wrapper’s subscript operator), and then it will set the value associated with a.
That’s all the property wrapper is doing, providing the get and set accessors. E.g., the declaration:
#AtomicDictionary var a: [String: Int]
translates to:
private var _a: AtomicDictionary<String, Int> = AtomicDictionary<String, Int>(wrappedValue: [:])
var a: [String: Int] {
get { return _a.wrappedValue }
set { _a.wrappedValue = newValue }
}
Any other methods you define are only accessible through _a in this example, not a (which is just a computed property that gets and sets the wrappedValue of _a).
So, you’re better off just defining a proper type for your “atomic dictionary”:
public class AtomicDictionary<Key: Hashable, Value> {
private var wrappedValue: [Key: Value]
private let queue = DispatchQueue(label: "atomicDictionary.\(UUID().uuidString)", attributes: .concurrent)
init(_ wrappedValue: [Key: Value] = [:]) {
self.wrappedValue = wrappedValue
}
public subscript(key: Key) -> Value? {
get {
queue.sync {
wrappedValue[key]
}
}
set {
queue.async(flags: .barrier) {
self.wrappedValue[key] = newValue
}
}
}
}
And
let a = AtomicDictionary<String, Int>()
That gives you the behavior you want.
And if you are going to supply CustomDebugStringConvertible conformance, make sure to use your synchronization mechanism there, too:
extension AtomicDictionary: CustomDebugStringConvertible {
public var debugDescription: String {
queue.sync { wrappedValue.debugDescription }
}
}
All interaction with the wrapped value must be synchronized.
Obviously you can use this general pattern with whatever synchronization mechanism you want, e.g., the above reader-writer pattern, GCD serial queue, locks, actors, etc. (The reader-writer pattern has a natural appeal, but, in practice, there are generally better mechanisms.)
Needless to say, the above presumes that subscript-level atomicity is sufficient. One should always be wary about general purpose thread-safe collections as often the correctness of our code relies on a higher-level of synchronization.

The most efficient way to build tree from dictionary data

I have a dictionary with the following structure: [Point:[Line]], where:
Point - custom data structure that contains two coordinates (X, Y)
Line - tuple (Point, Point) that contains the first and last points of the line.
Key - first point of the line.
So, it is a dictionary of lines grouped by their first point, like following:
[a: [(a,b),(a,c)], b: [(b,c), (b,d)], c: [(c,d)] ]
The goal is to convert this dictionary into a list of data structures like following:
(a,b) -> (b,c) -> (c,d)
(a,b) -> (b,d)
(a,c) -> (c,d)
So, basically a tree with the root at the first point.
I tried to use builder pattern and stack to perform this operation, so I created a builder using first point and put in into stack, then started a while loop until stack is empty and then in the loop was removing current builder and creating new ones based on last & first points, the code looked like following:
import Foundation
typealias ProcessedLine = (UUID, [LineModel])
typealias LinePointDict = [Point: [Line]]
class LineAggregator {
var builderStack: Stack<LineModel.LineModelBuilder>
let startPointMap: LinePointDict
var result: ProcessedLine
var currentBuilder: (Point, LineModel.LineModelBuilder)?
let startPoint: Point
init(LineUid: UUID, startPointMap: LinePointDict, startPoint: Point) {
self.builderStack = Stack<LineModel.LineModelBuilder>()
self.startPointMap = startPointMap
self.result = (LineUid, [])
self.startPoint = startPoint
self.currentBuilder = nil
}
func getLineAggregation() -> ProcessedLine {
for Line in startPointMap[startPoint]! {
var bldr = LineModel.LineModelBuilder(initialLineUuid: result.0)
bldr = bldr.addLine(Line: Line)
builderStack.push(bldr)
}
return aggregateModels()
}
func aggregateModels() -> ProcessedLine {
while !builderStack.isEmpty() {
takeBuilderFromStack()
aggregateLine()
}
return result
}
/**
* This functions pops Builder object from stack if the stack is not empty and sets it as a Current object to be processed.
* #param object
* #return
*/
private func takeBuilderFromStack() {
if(!builderStack.isEmpty()) {
let curBuilder = builderStack.pop()!
currentBuilder = (curBuilder.getLastElement(), curBuilder)
}
}
private func aggregateLine() {
if currentBuilder?.1.isLastAdded() ?? true {
//If there is only one Line in the Line model
if(currentBuilder!.1.isLastAddedLineLast()) {
result.1.append(currentBuilder!.1.build())
return
}
if(!builderStack.isEmpty()) {
print("ERROR: Empty builder stack! Such situation should not happen. Pay attention at it.");
builderStack.removeAll()
}
return
}
if currentBuilder != nil {
for Line in startPointMap[currentBuilder!.0]! {
var newBuilder = LineModel.LineModelBuilder(builder: currentBuilder!.1)
newBuilder = newBuilder.addLine(Line: Line)
if Line.isLast {
result.1.append(newBuilder.build())
} else {
builderStack.push(newBuilder)
}
}
}
}
}
This solution is a very straightforward one. It works, but I have a very large amount of data in the dictionary, and the number of combinations is even larger, so this algorithm is extremely slow and not memory efficient.
The main slowness is caused by adding and retrieving data to/from stack which has following implementation:
import Foundation
protocol Stackable {
associatedtype Element
func peek() -> Element?
mutating func push(_ element: Element)
#discardableResult mutating func pop() -> Element?
}
extension Stackable {
var isEmpty: Bool { peek() == nil }
}
struct Stack<Element>: Stackable where Element: Equatable {
private var storage = [Element]()
func peek() -> Element? { storage.first }
mutating func push(_ element: Element) { storage.append(element) }
mutating func pop() -> Element? { storage.popLast() }
func size() -> Int { storage.count }
func isEmpty() -> Bool { storage.isEmpty }
mutating func removeAll() { storage.removeAll() }
}
extension Stack: Equatable {
static func == (lhs: Stack<Element>, rhs: Stack<Element>) -> Bool { lhs.storage == rhs.storage }
}
extension Stack: CustomStringConvertible {
var description: String { "\(storage)" }
}
extension Stack: ExpressibleByArrayLiteral {
init(arrayLiteral elements: Self.Element...) { storage = elements }
}
And another bottleneck is related to copying data and deinit method.
I was trying to find a better solution, but couldn't find anything yet. Would be grateful for any suggestions. Thanks.
While the builder pattern is useful, I think in this case it just complicates the straight-forward solution, although as you'll see, I'll present a couple that are more complicated, but those are based on increased performance optimizations on the first simple solution.
As you you noted, initializing and deinitializing classes is kind of slow. Actually the worst part is the dynamic memory allocation. Classes are powerful and definitely have their uses, but they're not the fastest tool in the Swift toolbox. Unless you make methods final, calling them can require a virtual dispatch. That can happen with protocols too depending on the particulars of their declaration, though in that case it's called "witness table thunking". But the worst part about classes is that their instances can be littered pretty much anywhere in memory. That's hell on the processor's on-chip cache. So for performance try to avoid dynamic dispatch, and reference types (ie, classes), and when you do need to allocate memory (such as Array or Dictionary), try to allocate all you need at once, and reuse it as much as possible. In Swift that requres some thought because of copy-on-write. You can easily end up allocating memory when you didn't intend to.
I present three solutions. Each one is more complicated, but also (hopefully) faster than the previous one. I'll explain why, and what to look out for. I should mention that I am not including the simplest solution. It is much like my first solution but with local variable arrays. The performance would not be especially good, and you're question makes it clear that performance is an issue.
First there's some boiler plate. To code it up and test it, I needed to define your Point and Line types, plus a few others I use for convenience, as well as some extensions purely for generating output. This code is common to all three solutions. Substitue your own definitions for Point and Line.
struct Point: Hashable
{
// This is just to give the points uniqueness, and letter names
static private let pointNames = [Character]("abcdefghijklmnopqrstuvwxyz")
static private var curNameIndex = 0
static private var nextID: Character
{
defer { curNameIndex += 1 }
return pointNames[curNameIndex]
}
let id = nextID
}
typealias Line = (Point, Point)
typealias Graph = [Point: [Line]]
typealias Path = [Line]
// Now we add some extensions for convenient output
extension Point: CustomStringConvertible {
var description: String { "\(id)" }
}
extension String.StringInterpolation
{
mutating func appendInterpolation(_ line: Line) {
appendLiteral("(\(line.0),\(line.1))")
}
mutating func appendInterpolation(_ lines: [Line]) {
appendLiteral(lines.map { "\($0)" }.joined(separator: "->"))
}
}
You mention that Point has an X and Y, but for this problem it doesn't matter. You just need unique "things" to serve as end-points for your Line instances.
Then I declared the inputs from your example:
let (a, b, c, d) = (Point(), Point(), Point(), Point())
let input = [ a: [(a,b),(a,c)], b: [(b,c), (b,d)], c: [(c,d)] ]
With the common code out of the way, here are the actual solutions:
Solution 1
Although the recursion introduces overhead all by itself, the main problem with the most straight-forward solution is that it requres local arrays that are allocated and deallocated up and down the call stack. The dynamic memory allocations and deallocations for them are actually the main performance problem with the simple recursive solution.
The solution is to attempt to pre-allocate working storage, and re-use it all through-out the recursion, or at least make reallocations rare.
One way would be to allocate the working arrays at the top level and pass them in as inout parameters. Another is to make them mutable properties of a struct (actually, a class wouldn't be too bad in this case, because you only allocate one instance). I chose the latter approach.
As I mentioned in my comment, I think of this problem as a graph theory problem.. Point = node. Line = edge. Though it's not explicitly stated, I assume there are no cycles. Putting in code to detect cycles isn't that hard, but would complicate the solution slightly. I also assume that the output should not contain entries with just one Line, because your example doesn't include any examples of that.
struct PathFinderVersion1
{
private let graph: Graph
private var pathList = [Path]()
private var currentPath = Path()
private init(for graph: Graph)
{
self.graph = graph
self.pathList.reserveCapacity(graph.count)
self.currentPath.reserveCapacity(graph.count)
}
static func pathList(for graph: Graph) -> [Path]
{
var pathFinder = Self(for: graph)
return pathFinder.makePathLists()
}
private mutating func makePathLists() -> [Path]
{
for src in graph.keys
{
for edge in graph[src]!
{
assert(edge.0 == src, "sanity check failed")
currentPath.append(edge)
appendAllPaths()
currentPath.removeLast()
}
}
return pathList
}
private mutating func appendAllPaths()
{
assert(currentPath.count > 0, "currentPath must not be empty on entry")
guard let src = currentPath.last?.1 else { return }
guard let edges = graph[src] else
{
if currentPath.count > 1 {
pathList.append(currentPath)
}
return
}
for edge in edges
{
assert(edge.0 == src, "sanity check failed")
currentPath.append(edge)
appendAllPaths()
currentPath.removeLast()
}
}
}
Apart from the init, and static wrapper function, pathList(for:), the algorithm is really just two functions. The initializer is where I pre-allocate the working storage. Assuming there is an entry in the graph Dictionary for each Point, no path can ever be longer than there entries keys in the graph ... at least not without cycles, so currentPath is initialized with that much capacity. Similar thinking applies to the other working arrays. The pathList is likely to be be larger than graph.count, but unless there are a lot of unconnected Lines, it will need to be at least as big as graph is.
makePathLists() is the part that gets thing started, extracting the Point and array of Line for each of its entries. It initializes the first entry in a currentPath, then calls appendAllPaths() to recursively append new Line instances to currentPath on the way down. When it reaches the end, it has found a complete path, so it adds the currentPath to the pathList. On the way back up, it removes the entry it added from currentPath so that it's in a state that it can be reused again to go down another path in the graph.
Once makePathLists() has iterated over all its keys and appendAllPaths() has recursed for each one, pathList contains the result. I'm not 100% sure it's in the format you want. It's basically a flat list of all the paths it found. So it's kind of one data structure. But all the paths will be grouped together according to the starting point in the line, so splitting it into smaller lists is easy enough, if that's what you actually want.
In any case, the re-use of existing storage is where this version gets most of its performance.
You can call it like this:
print("Version 1")
for path in PathFinderVersion1.pathList(for: input) {
print("\(path)")
}
And here's the output for the data I set up as input:
Version 1
(b,c)->(c,d)
(a,b)->(b,c)->(c,d)
(a,b)->(b,d)
(a,c)->(c,d)
The exact order changes slightly from run-to-run because Dictionary doesn't necessarily hand out its keys in an order that is consistent from run to run, even if they are inserted exactly the same way every time. I tested each version to verify they all emit the same output (and so should you), but I won't include the output again for the other versions.
Solution 2
This version is based on solution 1, so everything about it applies to this version too. What's different is the addition of the dynamic programming technique of caching intermediate values. Basically I cache paths along the way, so that when I encounter them again, I don't have do all that recursion. I can just used the cached path instead.
There is one snag with this caching, it requres allocating some local caches, which introduces dynamic memory allocation again. However hope is not lost. For starters, assuming lots of nodes in the graph have multiple input edges (ie, lots of different lines connect to the same line), the result should be a win overall from being able to avoid a vast amount of recursion. Additionally i, re-use the local caches, so I only ever have to actually allocate a new one when I recurse deeper than the previous maximum depth reached. So while some allocation does happen, it's minimized.
All that cache handling makes the code longer, but faster.
To make the code more readable I put the local caching in nested struct. Here's the code for version 2:
struct PathFinderVersion2
{
private typealias CachedPath = Path.SubSequence
private typealias CachedPaths = Array<CachedPath>
private let graph: Graph
private var pathList = [Path]()
private var currentPath = Path()
private var pathCache = [Point: CachedPaths]()
private struct LocalPathCache
{
var cache = [CachedPath]()
var pathListIndex: Int
var curPathLength: Int
mutating func updateLocalPathCache(from pathList: [Path])
{
while pathListIndex < pathList.endIndex
{
let pathToCache =
pathList[pathListIndex][curPathLength...]
cache.append(pathToCache)
pathListIndex += 1
}
}
mutating func update(
mainCache: inout [Point: CachedPaths],
for src: Point)
{
if cache.count > 0 {
mainCache[src] = cache
}
}
}
private var localCaches = [LocalPathCache]()
private mutating func getLocalCache(
pathListIndex: Int,
curPathLength: Int) -> LocalPathCache
{
if var cache = localCaches.last
{
localCaches.removeLast()
cache.cache.removeAll(keepingCapacity: true)
cache.pathListIndex = pathListIndex
cache.curPathLength = curPathLength
return cache
}
return LocalPathCache(
pathListIndex: pathListIndex,
curPathLength: curPathLength
)
}
private mutating func freeLocalCache(_ cache: LocalPathCache) {
localCaches.append(cache)
}
private init(for graph: Graph)
{
self.graph = graph
self.pathList.reserveCapacity(graph.count)
self.currentPath.reserveCapacity(graph.count)
self.pathCache.reserveCapacity(graph.count)
}
static func pathList(for graph: Graph) -> [Path]
{
var pathFinder = Self(for: graph)
return pathFinder.makePathLists()
}
private mutating func makePathLists() -> [Path]
{
for src in graph.keys
{
for edge in graph[src]!
{
assert(edge.0 == src, "sanity check failed")
currentPath.append(edge)
appendAllPaths()
currentPath.removeLast()
}
}
return pathList
}
private mutating func appendAllPaths()
{
assert(currentPath.count > 0, "currentPath must not be empty on entry")
guard let src = currentPath.last?.1 else { return }
if updatePathListFromCache(for: src) { return }
guard let edges = graph[src] else
{
if currentPath.count > 1 {
pathList.append(currentPath)
}
return
}
var localCache = getLocalCache(
pathListIndex: pathList.endIndex,
curPathLength: currentPath.endIndex
)
defer { freeLocalCache(localCache) }
for edge in edges
{
assert(edge.0 == src, "sanity check failed")
currentPath.append(edge)
appendAllPaths()
currentPath.removeLast()
localCache.updateLocalPathCache(from: pathList)
}
localCache.update(mainCache: &pathCache, for: src)
}
mutating func updatePathListFromCache(for src: Point) -> Bool
{
if let cachedPaths = pathCache[src]
{
let curPathIndex = currentPath.endIndex
for path in cachedPaths
{
currentPath.append(contentsOf: path)
pathList.append(currentPath)
currentPath.removeSubrange(curPathIndex...)
}
return true
}
return false
}
}
Solution 3
Solutions 1 and 2 still use recursion. Recursion is elegant, and nice to think about, because you can express a problem as a slightly simpler problem plus a bit. But unless it's tail-recursive, compilers can't optimize it particularly well. So the solution to that problem is to use iteration instead of recursion.
Turning a recursive algorithm into an iterative one is not always so easy, and can result in ugly code. That is the definitely the case here. There might be a simpler iterative algorithm, but I don't know it. So I basically replaced recursion with a state machine + stack. I've used this technique before for recursive code the desperately needed to be faster, and it does work. The code is a total pain for a human to read and maintain, but compilers can optimize the hell out of it.
This version still uses the cached intermediate solutions from version 2.
struct PathFinderVersion3
{
private typealias CachedPath = Path.SubSequence
private typealias CachedPaths = Array<CachedPath>
private let graph: Graph
private var pathList = [Path]()
private var currentPath = Path()
private var pathCache = [Point: CachedPaths]()
private struct LocalPathCache
{
var cache = [CachedPath]()
var pathListIndex: Int
var curPathLength: Int
mutating func updateLocalPathCache(from pathList: [Path])
{
while pathListIndex < pathList.endIndex
{
let pathToCache =
pathList[pathListIndex][curPathLength...]
cache.append(pathToCache)
pathListIndex += 1
}
}
mutating func update(
mainCache: inout [Point: CachedPaths],
for src: Point)
{
if cache.count > 0 {
mainCache[src] = cache
}
}
}
private var localCaches = [LocalPathCache]()
private mutating func getLocalCache(
pathListIndex: Int,
curPathLength: Int) -> LocalPathCache
{
if var cache = localCaches.last
{
localCaches.removeLast()
cache.cache.removeAll(keepingCapacity: true)
cache.pathListIndex = pathListIndex
cache.curPathLength = curPathLength
return cache
}
return LocalPathCache(
pathListIndex: pathListIndex,
curPathLength: curPathLength
)
}
private mutating func freeLocalCache(_ cache: LocalPathCache) {
localCaches.append(cache)
}
private init(for graph: Graph)
{
self.graph = graph
self.pathList.reserveCapacity(graph.count)
self.currentPath.reserveCapacity(graph.count)
self.pathCache.reserveCapacity(graph.count)
}
static func pathList(for graph: Graph) -> [Path]
{
var pathFinder = Self(for: graph)
return pathFinder.makePathLists()
}
private mutating func makePathLists() -> [Path]
{
for src in graph.keys
{
for edge in graph[src]!
{
assert(edge.0 == src, "sanity check failed")
currentPath.append(edge)
appendAllPaths()
currentPath.removeLast()
}
}
return pathList
}
struct Stack<T>
{
var storage: [T] = []
var isEmpty: Bool { storage.isEmpty }
var count: Int { storage.count }
init(capacity: Int) { storage.reserveCapacity(capacity) }
mutating func push(_ element: T) { storage.append(element) }
mutating func pop() -> T? { storage.popLast() }
}
private mutating func appendAllPaths()
{
assert(currentPath.count > 0, "currentPath must not be empty on entry")
enum State
{
case entry
case inLoopPart1
case inLoopPart2
case exit
}
var state: State = .entry
typealias StackElement =
(Point, Int, Line, [Line], LocalPathCache, State)
var stack = Stack<StackElement>(capacity: graph.count)
var src: Point! = nil
var edges: [Line]! = nil
var edgeIndex: Int = 0
var edge: Line! = nil
var localCache: LocalPathCache! = nil
outer: while true
{
switch state
{
case .entry:
if let s = currentPath.last?.1 {
src = s
}
else
{
state = .exit
continue outer
}
if updatePathListFromCache(for: src)
{
state = .exit
continue outer
}
if let e = graph[src] { edges = e }
else
{
if currentPath.count > 1 {
pathList.append(currentPath)
}
state = .exit
continue outer
}
localCache = getLocalCache(
pathListIndex: pathList.endIndex,
curPathLength: currentPath.endIndex
)
edgeIndex = edges.startIndex
state = .inLoopPart1
continue outer
case .inLoopPart1:
if edgeIndex < edges.endIndex
{
edge = edges[edgeIndex]
assert(edge.0 == src, "sanity check failed")
currentPath.append(edge)
// Simulate function call
stack.push((src, edgeIndex, edge, edges, localCache, .inLoopPart2))
state = .entry
continue outer
}
localCache.update(mainCache: &pathCache, for: src)
state = .exit
case .inLoopPart2:
localCache.updateLocalPathCache(from: pathList)
edgeIndex += 1
state = .inLoopPart1 // Simulate goto top of inner loop
case .exit: // Simulate return
if let c = localCache { freeLocalCache(c) }
if let savedState = stack.pop()
{
(src, edgeIndex, edge, edges, localCache, state) = savedState
currentPath.removeLast()
}
else { break outer }
}
}
assert(stack.isEmpty)
}
mutating func updatePathListFromCache(for src: Point) -> Bool
{
if let cachedPaths = pathCache[src]
{
let curPathIndex = currentPath.endIndex
for path in cachedPaths
{
currentPath.append(contentsOf: path)
pathList.append(currentPath)
currentPath.removeSubrange(curPathIndex...)
}
return true
}
return false
}
}
Ummm... yeah, there it is in all its ugly glory. It works. It's fast. Good luck maintaining it.
The question is whether the speed is worth it when weighed against the readability issues and maintenance headaches, and that all depends on the application requirements. In my opinion that speed would have to be pretty darn important to put up with maintaining this version, and I'm normally fine with putting up with some ugliness to get speed in critical code. Somehow, this version, written in a language that doesn't even have a goto statement is nonetheless a poster-child for why goto is considered bad in the first place.
Solution 4 (Bonus)
Originally I just mentioned that you could parallelize it, but I didn't implement it. I decided that for completeness, a parallelization example really should be included.
I chose to parallelize solution 1, but all three of the previous solutions can be parallelized in exactly the same way. The changes that have to be made are to add the split method, and modify the static pathList(for:) method as well as the private makePathLists instance method. You also need a concurrent DispatchQueue. I create a global one for this example, but you can use an existing one. Don't use DispatchQueue.main for this, if you want your app to be responsive while processing.
Here's the code:
import Foundation
let dispatchQueue =
DispatchQueue(label: "PathFinder-\(UUID())",attributes: .concurrent)
struct PathFinderVersion4
{
private let graph: Graph
private var pathList = [Path]()
private var currentPath = Path()
private init(for graph: Graph)
{
self.graph = graph
self.pathList.reserveCapacity(graph.count)
self.currentPath.reserveCapacity(graph.count)
}
public static func pathList(for graph: Graph) -> [Path]
{
let concurrency = min(4, graph.count)
let pointGroups = split(.init(graph.keys), numberOfGroups: concurrency)
var pathLists = [[Path]](repeating: [], count: concurrency)
let waitSems = Array(
repeating: DispatchSemaphore(value: 0),
count: concurrency
)
for groupIndex in pointGroups.indices
{
dispatchQueue.async
{
defer { waitSems[groupIndex].signal() }
var pathFinder = Self(for: graph)
pathLists[groupIndex] =
pathFinder.makePathLists(for: pointGroups[groupIndex])
}
}
// Need to signal each semaphore after waiting or will crash on return.
// See Apple documentation for DispatchSemaphore
waitSems.forEach { $0.wait(); $0.signal() }
var result = [Path]()
result.reserveCapacity(pathLists.reduce(0) { $0 + $1.count })
pathLists.forEach { result.append(contentsOf: $0) }
return result
}
private static func split<Value>(
_ values: [Value],
numberOfGroups: Int) -> [[Value]]
{
var groups = [[Value]]()
groups.reserveCapacity(numberOfGroups)
let groupSize = values.count / numberOfGroups
+ (values.count % numberOfGroups == 0 ? 0 : 1)
var valueIndex = values.startIndex
while valueIndex < values.endIndex
{
var group = [Value]()
group.reserveCapacity(groupSize)
let valueEnd = min(valueIndex + groupSize, values.endIndex)
while valueIndex < valueEnd
{
group.append(values[valueIndex])
valueIndex += 1
}
groups.append(group)
}
return groups
}
private mutating func makePathLists(for points: [Point]) -> [Path]
{
for src in points
{
for edge in graph[src]!
{
assert(edge.0 == src, "sanity check failed")
currentPath.append(edge)
appendAllPaths()
currentPath.removeLast()
}
}
return pathList
}
private mutating func appendAllPaths()
{
assert(currentPath.count > 0, "currentPath must not be empty on entry")
guard let src = currentPath.last?.1 else { return }
guard let edges = graph[src] else
{
if currentPath.count > 1 {
pathList.append(currentPath)
}
return
}
for edge in edges
{
assert(edge.0 == src, "sanity check failed")
currentPath.append(edge)
appendAllPaths()
currentPath.removeLast()
}
}
}

How to implement a Thread Safe HashTable (PhoneBook) Data Structure in Swift?

I am trying to implement a Thread-Safe PhoneBook object. The phone book should be able to add a person, and look up a person based on their name and phoneNumber. From an implementation perspective this simply involves two hash tables, one associating name -> Person and another associating phone# -> Person.
The caveat is I want this object to be threadSafe. This means I would like to be able to support concurrent lookups in the PhoneBook while ensuring only one thread can add a Person to the PhoneBook at a time. This is the basic reader-writers problem, and I am trying to solve this using GrandCentralDispatch and dispatch barriers. I am struggling to solve this though as I am running into issues.. Below is my Swift playground code:
//: Playground - noun: a place where people can play
import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
public class Person: CustomStringConvertible {
public var description: String {
get {
return "Person: \(name), \(phoneNumber)"
}
}
public var name: String
public var phoneNumber: String
private var readLock = ReaderWriterLock()
public init(name: String, phoneNumber: String) {
self.name = name
self.phoneNumber = phoneNumber
}
public func uniquePerson() -> Person {
let randomID = UUID().uuidString
return Person(name: randomID, phoneNumber: randomID)
}
}
public enum Qos {
case threadSafe, none
}
public class PhoneBook {
private var qualityOfService: Qos = .none
public var nameToPersonMap = [String: Person]()
public var phoneNumberToPersonMap = [String: Person]()
private var readWriteLock = ReaderWriterLock()
public init(_ qos: Qos) {
self.qualityOfService = qos
}
public func personByName(_ name: String) -> Person? {
var person: Person? = nil
if qualityOfService == .threadSafe {
readWriteLock.concurrentlyRead { [weak self] in
guard let strongSelf = self else { return }
person = strongSelf.nameToPersonMap[name]
}
} else {
person = nameToPersonMap[name]
}
return person
}
public func personByPhoneNumber( _ phoneNumber: String) -> Person? {
var person: Person? = nil
if qualityOfService == .threadSafe {
readWriteLock.concurrentlyRead { [weak self] in
guard let strongSelf = self else { return }
person = strongSelf.phoneNumberToPersonMap[phoneNumber]
}
} else {
person = phoneNumberToPersonMap[phoneNumber]
}
return person
}
public func addPerson(_ person: Person) {
if qualityOfService == .threadSafe {
readWriteLock.exclusivelyWrite { [weak self] in
guard let strongSelf = self else { return }
strongSelf.nameToPersonMap[person.name] = person
strongSelf.phoneNumberToPersonMap[person.phoneNumber] = person
}
} else {
nameToPersonMap[person.name] = person
phoneNumberToPersonMap[person.phoneNumber] = person
}
}
}
// A ReaderWriterLock implemented using GCD and OS Barriers.
public class ReaderWriterLock {
private let concurrentQueue = DispatchQueue(label: "com.ReaderWriterLock.Queue", attributes: DispatchQueue.Attributes.concurrent)
private var writeClosure: (() -> Void)!
public func concurrentlyRead(_ readClosure: (() -> Void)) {
concurrentQueue.sync {
readClosure()
}
}
public func exclusivelyWrite(_ writeClosure: #escaping (() -> Void)) {
self.writeClosure = writeClosure
concurrentQueue.async(flags: .barrier) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.writeClosure()
}
}
}
// MARK: Testing the synchronization and thread-safety
for _ in 0..<5 {
let iterations = 1000
let phoneBook = PhoneBook(.none)
let concurrentTestQueue = DispatchQueue(label: "com.PhoneBookTest.Queue", attributes: DispatchQueue.Attributes.concurrent)
for _ in 0..<iterations {
let person = Person(name: "", phoneNumber: "").uniquePerson()
concurrentTestQueue.async {
phoneBook.addPerson(person)
}
}
sleep(10)
print(phoneBook.nameToPersonMap.count)
}
To test my code I run 1000 concurrent threads that simply add a new Person to the PhoneBook. Each Person is unique so after the 1000 threads complete I am expecting the PhoneBook to contain a count of 1000. Everytime I perform a write I perform a dispatch_barrier call, update the hash tables, and return. To my knowledge this is all we need to do; however, after repeated runs of the 1000 threads I get the number of entries in the PhoneBook to be inconsistent and all over the place:
Phone Book Entries: 856
Phone Book Entries: 901
Phone Book Entries: 876
Phone Book Entries: 902
Phone Book Entries: 912
Can anyone please help me figure out what is going on? Is there something wrong with my locking code or even worse something wrong with how my test is constructed? I am very new to this multi-threaded problem space, thanks!
The problem is your ReaderWriterLock. You are saving the writeClosure as a property, and then asynchronously dispatching a closure that calls that saved property. But if another exclusiveWrite came in during the intervening period of time, your writeClosure property would be replaced with the new closure.
In this case, it means that you can be adding the same Person multiple times. And because you're using a dictionary, those duplicates have the same key, and therefore don't result in you're seeing all 1000 entries.
You can actually simplify ReaderWriterLock, completely eliminating that property. I’d also make concurrentRead a generic, returning the value (just like sync does), and rethrowing any errors (if any).
public class ReaderWriterLock {
private let queue = DispatchQueue(label: "com.domain.app.rwLock", attributes: .concurrent)
public func concurrentlyRead<T>(_ block: (() throws -> T)) rethrows -> T {
return try queue.sync {
try block()
}
}
public func exclusivelyWrite(_ block: #escaping (() -> Void)) {
queue.async(flags: .barrier) {
block()
}
}
}
A couple of other, unrelated observations:
By the way, this simplified ReaderWriterLock happens to solves another concern. That writeClosure property, which we've now removed, could have easily introduced a strong reference cycle.
Yes, you were scrupulous about using [weak self], so there wasn't any strong reference cycle, but it was possible. I would advise that wherever you employ a closure property, that you set that closure property to nil when you're done with it, so any strong references that closure may have accidentally entailed will be resolved. That way a persistent strong reference cycle is never possible. (Plus, the closure itself and any local variables or other external references it has will be resolved.)
You're sleeping for 10 seconds. That should be more than enough, but I'd advise against just adding random sleep calls (because you never can be 100% sure). Fortunately, you have a concurrent queue, so you can use that:
concurrentTestQueue.async(flags: .barrier) {
print(phoneBook.count)
}
Because of that barrier, it will wait until everything else you put on that queue is done.
Note, I did not just print nameToPersonMap.count. This array has been carefully synchronized within PhoneBook, so you can't just let random, external classes access it directly without synchronization.
Whenever you have some property which you're synchronizing internally, it should be private and then create a thread-safe function/variable to retrieve whatever you need:
public class PhoneBook {
private var nameToPersonMap = [String: Person]()
private var phoneNumberToPersonMap = [String: Person]()
...
var count: Int {
return readWriteLock.concurrentlyRead {
nameToPersonMap.count
}
}
}
You say you're testing thread safety, but then created PhoneBook with .none option (achieving no thread-safety). In that scenario, I'd expect problems. You have to create your PhoneBook with the .threadSafe option.
You have a number of strongSelf patterns. That's rather unswifty. It is generally not needed in Swift as you can use [weak self] and then just do optional chaining.
Pulling all of this together, here is my final playground:
PlaygroundPage.current.needsIndefiniteExecution = true
public class Person {
public let name: String
public let phoneNumber: String
public init(name: String, phoneNumber: String) {
self.name = name
self.phoneNumber = phoneNumber
}
public static func uniquePerson() -> Person {
let randomID = UUID().uuidString
return Person(name: randomID, phoneNumber: randomID)
}
}
extension Person: CustomStringConvertible {
public var description: String {
return "Person: \(name), \(phoneNumber)"
}
}
public enum ThreadSafety { // Changed the name from Qos, because this has nothing to do with quality of service, but is just a question of thread safety
case threadSafe, none
}
public class PhoneBook {
private var threadSafety: ThreadSafety
private var nameToPersonMap = [String: Person]() // if you're synchronizing these, you really shouldn't expose them to the public
private var phoneNumberToPersonMap = [String: Person]() // if you're synchronizing these, you really shouldn't expose them to the public
private var readWriteLock = ReaderWriterLock()
public init(_ threadSafety: ThreadSafety) {
self.threadSafety = threadSafety
}
public func personByName(_ name: String) -> Person? {
if threadSafety == .threadSafe {
return readWriteLock.concurrentlyRead { [weak self] in
self?.nameToPersonMap[name]
}
} else {
return nameToPersonMap[name]
}
}
public func personByPhoneNumber(_ phoneNumber: String) -> Person? {
if threadSafety == .threadSafe {
return readWriteLock.concurrentlyRead { [weak self] in
self?.phoneNumberToPersonMap[phoneNumber]
}
} else {
return phoneNumberToPersonMap[phoneNumber]
}
}
public func addPerson(_ person: Person) {
if threadSafety == .threadSafe {
readWriteLock.exclusivelyWrite { [weak self] in
self?.nameToPersonMap[person.name] = person
self?.phoneNumberToPersonMap[person.phoneNumber] = person
}
} else {
nameToPersonMap[person.name] = person
phoneNumberToPersonMap[person.phoneNumber] = person
}
}
var count: Int {
return readWriteLock.concurrentlyRead {
nameToPersonMap.count
}
}
}
// A ReaderWriterLock implemented using GCD concurrent queue and barriers.
public class ReaderWriterLock {
private let queue = DispatchQueue(label: "com.domain.app.rwLock", attributes: .concurrent)
public func concurrentlyRead<T>(_ block: (() throws -> T)) rethrows -> T {
return try queue.sync {
try block()
}
}
public func exclusivelyWrite(_ block: #escaping (() -> Void)) {
queue.async(flags: .barrier) {
block()
}
}
}
for _ in 0 ..< 5 {
let iterations = 1000
let phoneBook = PhoneBook(.threadSafe)
let concurrentTestQueue = DispatchQueue(label: "com.PhoneBookTest.Queue", attributes: .concurrent)
for _ in 0..<iterations {
let person = Person.uniquePerson()
concurrentTestQueue.async {
phoneBook.addPerson(person)
}
}
concurrentTestQueue.async(flags: .barrier) {
print(phoneBook.count)
}
}
Personally, I'd be inclined to take it a step further and
move the synchronization into a generic class; and
change the model to be an array of Person object, so that:
The model supports multiple people with the same or phone number; and
You can use value types if you want.
For example:
public struct Person {
public let name: String
public let phoneNumber: String
public static func uniquePerson() -> Person {
return Person(name: UUID().uuidString, phoneNumber: UUID().uuidString)
}
}
public struct PhoneBook {
private var synchronizedPeople = Synchronized([Person]())
public func people(name: String? = nil, phone: String? = nil) -> [Person]? {
return synchronizedPeople.value.filter {
(name == nil || $0.name == name) && (phone == nil || $0.phoneNumber == phone)
}
}
public func append(_ person: Person) {
synchronizedPeople.writer { people in
people.append(person)
}
}
public var count: Int {
return synchronizedPeople.reader { $0.count }
}
}
/// A structure to provide thread-safe access to some underlying object using reader-writer pattern.
public class Synchronized<T> {
/// Private value. Use `public` `value` computed property (or `reader` and `writer` methods)
/// for safe, thread-safe access to this underlying value.
private var _value: T
/// Private reader-write synchronization queue
private let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".synchronized", qos: .default, attributes: .concurrent)
/// Create `Synchronized` object
///
/// - Parameter value: The initial value to be synchronized.
public init(_ value: T) {
_value = value
}
/// A threadsafe variable to set and get the underlying object, as a convenience when higher level synchronization is not needed
public var value: T {
get { reader { $0 } }
set { writer { $0 = newValue } }
}
/// A "reader" method to allow thread-safe, read-only concurrent access to the underlying object.
///
/// - Warning: If the underlying object is a reference type, you are responsible for making sure you
/// do not mutating anything. If you stick with value types (`struct` or primitive types),
/// this will be enforced for you.
public func reader<U>(_ block: (T) throws -> U) rethrows -> U {
return try queue.sync { try block(_value) }
}
/// A "writer" method to allow thread-safe write with barrier to the underlying object
func writer(_ block: #escaping (inout T) -> Void) {
queue.async(flags: .barrier) {
block(&self._value)
}
}
}
In some cases you use might NSCache class. The documentation claims that it's thread safe:
You can add, remove, and query items in the cache from different threads without having to lock the cache yourself.
Here is an article that describes quite useful tricks related to NSCache
I don’t think you are using it wrong :).
The original (on macos) generates:
0 swift 0x000000010c9c536a PrintStackTraceSignalHandler(void*) + 42
1 swift 0x000000010c9c47a6 SignalHandler(int) + 662
2 libsystem_platform.dylib 0x00007fffbbdadb3a _sigtramp + 26
3 libsystem_platform.dylib 000000000000000000 _sigtramp + 1143284960
4 libswiftCore.dylib 0x0000000112696944 _T0SSwcp + 36
5 libswiftCore.dylib 0x000000011245fa92 _T0s24_VariantDictionaryBufferO018ensureUniqueNativeC0Sb11reallocated_Sb15capacityChangedtSiF + 1634
6 libswiftCore.dylib 0x0000000112461fd2 _T0s24_VariantDictionaryBufferO17nativeUpdateValueq_Sgq__x6forKeytF + 1074
If you remove the ‘.concurrent’ from your ReaderWriter queue, "the problem disappears”.©
If you restore the .concurrent, but change the async invocation in the writer side to be sync:
swift(10504,0x70000896f000) malloc: *** error for object 0x7fcaa440cee8: incorrect checksum for freed object - object was probably modified after being freed.
Which would be a bit astonishing if it weren’t swift?
I dug in, replaced your ‘string’ based array with an Int one by interposing a hash function, replaced the sleep(10) with a barrier dispatch to flush any laggardly blocks through, and that made it more reproducibly crash with the somewhat more helpful:
x(10534,0x700000f01000) malloc: *** error for object 0x7f8c9ee00008: incorrect checksum for freed object - object was probably modified after being freed.
But when a search of the source revealed no malloc or free, perhaps the stack dump is more useful.
Anyways, best way to solve your problem: use go instead; it actually makes sense.

Swift override all setters and getters of a subclass

I would like to override a setter/getter one time for but for all the properties for a class in swift
This my class. I want to call Realm each time I am adding a new value
class House : Object
{
var a:String
{
set {
do {
let realm = try Realm()
try realm.write {
a = newValue
}
}
catch {
}
}
}
var b:String
{
set {
do {
let realm = try Realm()
try realm.write {
b = newValue
}
}
catch {
}
}
}
}
There is no way in Swift how you can overwrite setters for all properties at once.
What you could generally do though is use:
overwritten setters per property
abstract computed properties wrapping low-level properties
intercept getters and setters by KVC accessor methods (e.g. is<Key>, get<Key>, …) and rely only on untyped dynamic KVC-based access via valueForKey(Path):, if you want to apply the decorated behavior (which you might want to avoid for this reason)
But Realm is using custom getters and setters under the hood, which are dynamically overwritten in an dynamically inserted intermediate class at runtime and relies on the presence of those. So the only approach, which is really feasible is having dynamic stored properties declared and adding for each of those an extra property, based on those.
var storedPropertyA: String = ""
var computedPropertyA: String {
get {
// add your extra behavior here
return storedPropertyA
}
set {
// add your extra behavior here
self.storedPropertyA = newValue
}
}
Beside that there is an alternative way of using the decorator pattern and decorate your whole object with extra behavior. In Swift, you could have your object and your decorator implement a common protocol, which defines your properties.
protocol HousingProperties {
var a: String { get set }
}
class House: HousingProperties {
var a: String = ""
}
class HouseDecorator: HousingProperties {
internal var house: House
init(house: House) { self.house = house }
var a: String {
// add your extra behavior here
self.house.a = a
}
}
Still I would NOT recommend to intercept property setters and getters for the purpose you intend here. Instead I'd advise to structure your application's architecture in a way, that allows you to be aware whether there is a write transaction or not and let the responsibility of making a write transaction in the hands of the code, which tries to modify objects.
Let me explain why:
Realm is using a multiversion concurrency control algorithm to manage persisted data and achieve thread-safety. This makes sure that different threads can read data at any point in time without having to read-lock and trying to synchronize these. Instead when a write is happening, all accessors are notified that there is new data and try to move on to the newest transaction. Until that has happened, all versions between the oldest data version, which is still used by a thread and the one written have to be retained. They can be first released when all threads advanced their commit pointers. If you do a lot of small transactions, you risk that your file size will blew up to unnecessary high values. For that reason, we recommend to batch write transactions to large changesets.
There is one hack to kind of attain what the poster is looking for, however possibly not advisable... Anyway; you can can create your own assignment operators that does whatever you want to do in realm prior to assigning the values
class MyType {
var myInt : Int = 0
var myString : String = ""
init(int: Int, string: String) {
myInt = int
myString = string
}
}
infix operator === {}
func ===<T>(lhs: T, rhs: T) -> T? {
Realm() // replace with whatever Realm()-specific stuff you want to do
return rhs
}
protocol MyAddableTypes {
func + (lhs: Self, rhs: Self) -> Self
}
extension String : MyAddableTypes {}
extension Int : MyAddableTypes {}
infix operator +== {} // ... -== similarily
func +==<T: MyAddableTypes>(lhs: T, rhs: T) -> T? {
Realm() // replace with whatever Realm()-specific stuff you want to do
return lhs+rhs
}
func Realm() {
// ...
print("Called realm")
}
var a = MyType(int: 1, string: "foo")
a.myString === "bar" // calls Realm(). After operation: a.myString = "bar"
a.myInt +== 1 // calls Realm(). After operation: a.myInt = 2
I thought I'd also mention that if you only want to do "Realm stuff" when a value is set (from your example: prior to setting a value, specifically), then the willSet method, used with stored properties, doesn't need to look so messy (nested closures), and personally, I would prefer this method
func Realm() {
print("Called realm")
}
class MyType {
// This isn't so messy, is it?
var myInt : Int = 0 { willSet { priorToSetValue(newValue) } }
var myString : String = "" { willSet { priorToSetValue(newValue) } }
var myDouble : Double = 0.0 { willSet { priorToSetValue(newValue) } }
private func priorToSetValue<T> (myVar: T) {
// replace with whatever Realm()-specific stuff you want to do,
// possibly including doing something with your new value
Realm()
}
init(int: Int, double: Double, string: String) {
myInt = int
myDouble = double
myString = string
}
}
var a = MyType(int: 1, double: 1.0, string: "foo")
a.myString = "bar"
print(a.myString) // calls Realm(). After operation: a.myString = "bar"
a.myInt += 1 // calls Realm(). After operation: a.myInt = 2