How to avoid Data Race?

Hello. I have a piece of program where I need to pass an array to two async queues and at the end of execution of that queues I need to synchronize that array with the main queue. But I get the error Data race in Runner.Forum.run() -> () at 0x7b0800029860 and I absolutely don't know how to do it with out the error. I attached the code

struct Forum {
    let queue1 = DispatchQueue(label: "Pollination")
    let queue2 = DispatchQueue(label: "Return to the hive")
    struct Bee { let name: String }
    struct Swarm { var bees = [Bee]() }
    func run() {
    
    let bee1 = Bee(name: "1")
    let bee2 = Bee(name: "2")
    let bee3 = Bee(name: "3")
    let bee4 = Bee(name: "4")
    
    var swarm = Swarm()
    var copy2 = swarm
    var copy1 = swarm
    
    queue1.async {
        copy1.bees.append(bee1)
        copy1.bees.append(bee2)
        print("queue1: \(copy1)")
        swarm = copy1 // need synchronies the result of that queue
    }
    
    queue2.async {
        copy2.bees.append(bee3)
        copy2.bees.append(bee4)
        print("queue2: \(copy2)")
    }
    
    let _ = readLine() // need press button and Enter here
    print("after swarm: \(swarm)") // Data race in Runner.Forum.run() -> () at 0x7b0800029860
    print("after copy1: \(copy1)")
    print("after copy2: \(copy2)")
}

Arrays are copy on write, so they will use the same internal buffer until one of the "copies" are modified, then it will copy the whole internal buffer to a new place. So in order to avoid accessing the same internal buffer from multiple queues, you have to force a copy.

The easiest is to just append then remove an element from one of them.

But if you want a generic makeUnique function for Array, you can use this initializer to avoid double initialization or multiple resizes: init(unsafeUninitializedCapacity:initializingWith:) | Apple Developer Documentation

1 Like

ha ha. Does it really work?

I don't think that CoW is the issue here. Yes, the arrays will share a reference to the same buffer initially, but all attempts to write to that buffer will be guarded by an atomic uniqueness check, which will fail here (possibly multiple times) and they will start to copy. While they're copying though, the accesses to the shared memory are all reads, so even if there are multiple of them, it is safe.

What I believe is to be happening here is that there are parallel reads/writes to the struct Forum itself, since its subfields get captured by reference into the various async invocations. I believe that locking around swarm = copy1 and the three prints downstairs should help, since otherwise all writes occur to unrelated memory.


On a separate thought, I'm not sure about the lifetimes. The whole struct might get deallocated before it's accessed by the other threads.

1 Like

Сopying the code to the main.swift and removing run() function gets the same Data Race warning. But it is indeed read access. So maybe the warning is not critical here. But there is, and question is what does it mean. And another question is how to correct sync the result to the main queue. Because simple sync makes nonsense the background execution of the next tasks.

But it will cause Access Race and crash

This should help:

    func run() {
        
        let bee1 = Bee(name: "1")
        let bee2 = Bee(name: "2")
        let bee3 = Bee(name: "3")
        let bee4 = Bee(name: "4")
        
        var swarm = Swarm()
        var copy2 = swarm
        var copy1 = swarm
        
        let group = DispatchGroup()
        
        group.enter()
        queue1.async {
            copy1.bees.append(bee1)
            copy1.bees.append(bee2)
            print("queue1: \(copy1)")
            swarm = copy1
            group.leave()
        }
        
        group.enter()
        queue2.async {
            copy2.bees.append(bee3)
            copy2.bees.append(bee4)
            print("queue2: \(copy2)")
            group.leave()
        }
        
        group.wait()

        print("after swarm: \(swarm)")
        print("after copy1: \(copy1)")
        print("after copy2: \(copy2)")
        
    }
1 Like
Terms of Service

Privacy Policy

Cookie Policy