As per Wikipedia, race condition is defined as the condition of an electronics, software, or other systems where the system’s substantive behavior is dependent on the sequence or timing of other uncontrollable events. Race condition falls under the “Concurrency” department. Concurrency is the art of making progress on multiple tasks simultaneously. Let’s understand what concurrency actually means.
Consider the following scenario to understand concurrency in a better way. Imagine a basket full of laddoos and two people standing near the basket. One is assigned a task to measure the count, quality, weight, and examine other important features of each and every laddu in the basket. Now as soon as he begins to observe the laddoos to derive some conclusion, simultaneously the second person tampers some laddoos shape, beats them so they’re powdered, and even eats some bits of some laddoos. It’s happening simultaneously: one taking note of the features of laddoos & the other damaging the original features. Now there’s no way in the world that the first one knows the original data as he’s only taking note of the tampered data which even he is unaware of. This is a race condition. Both tasks executing simultaneously interfering with some common things leading to a great deal of data loss.
Lets code and understand race condition practically!
package main
import (
"fmt"
"time"
)
func execute(some string ) {
for i := 1 ; true; i++ {
fmt.Println(some, i)
time.Sleep(time.Millisecond * 100 )
}
}
func main() {
execute( "First" )
execute( "Second" )
fmt.Println( "program ends successfully" )
}
|

If you use Windows, you’re likely to see such output on the screen. The above output goes on like that and has no end unless you manually stop it or it runs out of system memory for execution.
Consider the second code.
package main
import (
"fmt"
"time"
)
func execute(some string ) {
for i := 1 ; true; i++ {
fmt.Println(some, i)
time.Sleep(time.Millisecond * 100 )
}
}
func main() {
go execute( "First" )
execute( "Second" )
fmt.Println( "program ends successfully" )
}
|

If you use Windows, you’re likely to see such output on the screen. When you create a goroutine in Go, it typically makes the execution faster by giving off the waiting time to other prevailing tasks, Now let’s see what happens if you create two goroutines in the same program.
package main
import (
"fmt"
"time"
)
func execute(some string ) {
for i := 1 ; true; i++ {
fmt.Println(some, i)
time.Sleep(time.Millisecond * 100 )
}
}
func main() {
go execute( "First" )
go execute( "Second" )
fmt.Println( "Program ends successfully" )
}
|

If you use Windows, you’re likely to see such output on the screen. Let’s consider one race condition scenario to wind up our topic:
package main
import (
"fmt"
"runtime"
"sync"
)
var wgIns sync.WaitGroup
func main() {
counter := 0
wgIns.Add( 10 )
for i := 0 ; i < 10 ; i++ {
go func () {
for j := 0 ; j < 10 ; j++ {
counter += 1
}
wgIns.Done()
}()
}
fmt.Println( "The number of goroutines before wait = " , runtime.NumGoroutine())
wgIns.Wait()
fmt.Println( "Counter value = " , counter)
fmt.Println( "The number of goroutines after wait = " , runtime.NumGoroutine())
}
|

This inconsistency happens because of race conditions. Race condition in simple terms can be explained as, you have one candy and two kids run to you claiming that they’re both hungry. They both end up fighting for that one chocolate, they race to get the candy. This is a race condition. Here the solution is: get another candy so that the two enjoy a candy peacefully. Likewise, we can increase the resources allocation in order to ensure race condition does not occur.