Race Condition
Learn about the race condition.
We'll cover the following
What is a race condition?
A race condition occurs when two or more threads have access to the same data and both try to update it simultaneously. We don’t know the order in which the threads will attempt to access the shared data because the scheduling algorithm can switch between threads. As a result, the outcome is dependent on the scheduling method of the thread, which means that both threads race to access and alter the data.
Race conditions are a very common problem in concurrent and parallel programming. When poorly planned, concurrent programming can cause huge problems since it utilizes the CPU core to the maximum extent.
function update(){
x = 100
thread_decrement(){
x = x - 50
}
thread_increment(){
x = x + 15
}
}
The pseudocode above has two threads. In one thread, an increment function runs, while in the other, a decrement function runs. The scheduler is preemptive in nature. It could preempt the process if it’s waiting for some other resources in order to use the CPU core concurrently.
In the code section above, three scenarios can take place. Variable x
is the shared resource between the two processes. The three scenarios are as follows:
- Everything works in order. The update function starts executing, then the decrement function starts running and changes the value of the
x
variable to 50. Then the scheduler preempts the decrement function, and the increment function executes and changes the value to 65.
- The value of variable
x
is 100. The decrement function starts its execution and reads the value of variablex
. But before it can update its value, preemption takes place. The increment function starts its execution and changes the value to 115. Again, it will go to the decrement function. The value of the variable stored with the decrement function is 100. It will update the value of variablex
from 115 to 50. The condition is a race condition because the two functions are racing to update the value.
- The value of variable
x
is 100. The decrement function starts its execution and reads the value of variable x. But before it can update its value, preemption takes place. The increment function starts its execution. Again, the value read by the increment function is 100, but the preemption happens before it can update the value ofx
. The scheduler starts the execution of the decrement function. It will update the value of variablex
from 100 to 50. Again, it will move to the increment function. The value ofx
stored with the increment function is 100, and it will change the value from 50 to 115.
Race condition in Golang
Let’s look at a practical example to understand race conditions better.
package mainimport ("time")// This is an example of race condition// 2 goroutines try to write and there is no access control.var x int = 0func decrement() {for {x = x - 50}}func increment() {for {x = x + 15}}func main() {go increment()go decrement()time.Sleep(5 * time.Second)}
The code above contains a race condition (lines 14 and 20). The two threads are racing to update the value which causes one to overlap with the other. It may run in the correct order, but there’s no guarantee.
Since we want the Go compiler to detect the race condition, we’ll run it with the race flag (go run -race main.go)
. It will display the following error:
$ go run -race main.go
==================
WARNING: DATA RACE
Read at 0x0000005464b8 by goroutine 6:
main.increment()
/usercode/main.go:20 +0x29
Previous write at 0x0000005464b8 by goroutine 7:
main.decrement()
/usercode/main.go:14 +0x44
Goroutine 6 (running) created at:
main.main()
/usercode/main.go:25 +0x29
Goroutine 7 (running) created at:
main.main()
/usercode/main.go:26 +0x35
==================
Found 1 data race(s)
exit status 66
Explanation
The important lines in the code:
-
Line 14: We try to decrease the value of the shared variable. The decrement function accesses the
x
variable without synchronization. -
Line 20: We try to increase the value of the shared variable. The increment function accesses the
x
variable without synchronization. -
Line 27: We add the
time.Sleep
function to stop the main goroutine from exiting the program.