HàPhan 河

What is a race condition and how do we avoid it?

Take a look at the picture below, there are 3 concurrent tasks running. They start at different time also complete at different time.

Screen-Shot-2019-12-31-at-12.55.34-PM

Imagine the case all of them are using same resource, t1 and t3 update, t2 reads.

Screen-Shot-2019-12-31-at-1.02.35-PM

We could not predict the value of resource when T2 access it. That's race condition. I prepared an example with Swift GCD, just take a look.

func test() {
    var resource: Int = 0

    let queue1 = DispatchQueue.init(label: "ex.queue1", attributes: .concurrent)
    let queue2 = DispatchQueue.init(label: "ex.queue2", attributes: .concurrent)
    let queue3 = DispatchQueue.init(label: "ex.queue3", attributes: .concurrent)

    queue1.async {
        usleep(UInt32(1e6))
        resource = 8
    }

    queue2.async {
        usleep(UInt32(1e6))
        print(resource)
    }

    queue3.async {
        resource = 10
        usleep(UInt32(1e6))
    }
}


for _ in 0...10 {
    test()
}

Console log will print 8 or 10 in random order. It will be a big problem when developer real app.
How do we prevent this problem?
First approach, we could make all queue synchronize. Only one read/write process could access the resource at same time.

Let make task 2 synchronous

queue1.async {
    usleep(UInt32(1e6))
    resource = 8
}

queue2.sync {
    usleep(UInt32(1e6))
    print(resource)
}

queue3.async {
    resource = 10
    usleep(UInt32(1e6))
}

Result always 0, because async task not complete before sync task start. We always get origin data. Let delay task2 a bit.

queue2.sync {
    usleep(UInt32(1e6 + 10))
    print(resource)
}

8 is always printed, 10 is never printed. That means task 3 need to wait for task 2 complete. At that time task1 already completed, so we get 8.

Thanks for reading.

Comments