Into the threads with Go - (Readers-Writers problem)

Into the threads with Go - (Readers-Writers problem)

It's a common Synchronization issue when you are working with multiple threads and accessing shared resources. If one of the threads is updating a shared resource no other thread should be reading or updating at the same time cause the changes made by that thread will not be visible. In that case, we may not get the desired output. So there can be multiple readers at the same time but only one writer.

We can deep dive into this problem with GoLang. GoLang provides Goroutines which is a lightweight thread managed by Go runtime.

package main

import ( 
    "fmt" 
    "sync"
)
var wg=sync.WaitGroup{}
var counter=0

func main() {
     for i:=0 ; i<5 ; i++ {
        wg.Add(2) // adding 2 waitgroups  for read and write operations  
        go write() // create Goroutine for write operation 
           go read() // create Goroutine for read operation
     }
     wg.Wait() // wait on the main Goroutine. until wait group counter become 0
}

func read() {

     fmt.Println("counter : ",counter)// reading counter 
     wg.Done() // decrease waitgroup counter
}

func write(){
      counter++ // updating counter
      wg.Done() 
}

In this code two goroutines are created in each iteration for reading and writing so we may not get the output as per our need. We can fix the problem of using MUTEX ( mutual exclusion ) in GoLang. It's basically a locking operation that ensures only one goroutine can access a critical section ( segment of code that contains shared variables ). In Go, we can use two types of locking mechanisms Mutex and RWMutex. RWMutex is the best choice for our condition cause in this scenario we want to execute multiple reader goroutines.

package main
import ( 
    "fmt" 
    "sync"
)

var wg=sync.WaitGroup{}
var counter=0
var mutex=sync.RWMutex{}

func main(){
     for i:=0 ; i<5 ; i++ {
        wg.Add(2)
        mutex.RLock() // multiple go routine can perform read / one go routine can perform at a time by acquiring the lock. 
        go read()
        mutex.Lock() // only one go routine can perform read (writing is not available) at a time by acquiring the lock. 
        go write()
     }
     wg.Wait()
}


func read(){
  fmt.Println("counter : ",counter)
  mutex.RUnlock() // reading is done other goroutine can write now
  wg.Done()
}
0
func write(){
    counter++
    mutex.Unlock() // writing is done
    wg.Done()
}

Now Only one writer Goroutine can access the critical section and others will wait at that time. Finally, we get the desired output multiple Goroutine can read the data at the same time. There isn't any race condition happening.

But mutex can lead us to starvation, and CPU cycle wastage. It's a good choice when we need access to a critical section.