GoLang Concurrency

In Chapter-5 of our Golang Tutorial, we touched upon ‘Data Structures’. In this chapter, let’s explore ‘Concurrency’ in Golang. Here we go –
Large programs are made up of small programs. For example, a web server handles number of requests made from browser and returns the responses. Every request is like a small programs that is handled.
It is always best to run the small components at same time (like web server handles the multiple requests at same time). So, working on the multiple programs simultaneously is known as concurrency.
main.go
 package main
import “fmt”
func main(){
 show()
 display()
 }
 func show(){
 for i:=0;i<100;i++{
 fmt.Println(“Show:”,i)
 }
 }
 func display(){
 for i:=0;i<100;i++{
 fmt.Println(“Display:”,i)
 }
}
If you observe in above program, the functions are executed sequentially i.e. one after another. Function Display( ) will wait till complete execution of Show( ). That will increase waiting time and could affect the performance of program.
For this, Go has great support and enhancement for concurrency using goroutines and channels.

Goroutines –

Goroutine is like thread concept in java which is capable of running with the multiple independent functions. To make function as a goroutine, you just need to add go keyword before the function.
Every program contains one go routine with func main( ). Now in below programs we have declared two more goroutines.
main.go
package main
import "fmt"
func main() {
 go show()
 go display()
}
func show() {
 for i := 0; i < 100; i++ {
 fmt.Println("show:", i)
 }
}
func display() {
 for i := 0; i < 100; i++ {
 fmt.Println("display:", i)
 }
}
In the above program. show() & display() will run independently as goroutine and give fast output but you can’t predict what output would come because they are working independently and it’ll be a mixed output.
Goroutines are lightweight and we can create thousands of them (any number). Goroutines has their own private stack and registers like thread and will execute from that stack only. If main goroutine exits, the program will also exit.

WaitGroup –

WaitGroup is the good concept in goroutines that will wait for other goroutines to finish their execution. Sometimes, for executing one activity, other activities need to complete.
As WaitGroup waits for no. of Goroutines to complete their execution, the main goroutine calls Add to set no. of goroutines to wait for. Then each goroutine runs and calls Done when finished their execution. At same time it will call Wait to block and wait until all goroutines finish their execution.
To use sync.WaitGroup :
  • Create instance of sync.WaitGroup      → var wg sync.WaitGroup
  • Call Add(n) where n is no of goroutines to wait for  → wg.Add(1)
  • Execute defer Wg.Done( ) in each goroutine to indicate that goroutine is finished
  • executing to the WaitGroup.
  • Call wg.Wait( ) where we want to block
main.go
 package main
import (
 "fmt"
 "sync"
)
var w sync.WaitGroup
func show(n int) {
 for i := 0; i < n; i++ {
 fmt.Println("fun show:", i)
 }
 defer w.Done()
}
func display(n int) {
 for i := 0; i < n; i++ {
 fmt.Println("fun display:", i)
 }
 defer w.Done()
}
func main() {
 fmt.Println("Calling with sync WaitGroup")
 w.Add(2)
 go show(50)
 go display(50)
 w.Wait()
 for i := 1; i <= 10; i++ {
 fmt.Println("For", i)
 }
}
In the above program , if we observe the show( ) and display( ) are goroutines which are added to WaitGroup that means main goroutine function have to wait till completion of this goroutines. When this function calls Defer.Done( ) it, indicates they are done with their execution and main goroutine also have to call Wait( ) on that waitgroup so that it will keep itself blocked.

Concurrency vs Parallelism

Concurrency is the composition of independently executing processes, while parallelism is the simultaneous execution of (possibly related) computations. Concurrency is dealing with lots of things at once. Parallelism is about doing things at once.
GoLang Concurrency
For parallelism , we just need to runtime.GOMAXPROCS(runtime.NumCPU()) in func init( ). init() is a function which is used to define some initialization program. Basically init () runs first before execution of main(). In init( ) we can provide some setup initialization logic.

Race Condition –

A race condition occurs when two or more routines tries to access the resource like variable or data structure and attempt to read or write the resources without regard to other routine. So it will create tremendous problems.
So Golang tooling introduced race detector. Race detector is code that is built in your program during build process. Once your program starts, it will start to detect race condition. It is really superb tool and does a great job.
For detecting whether there is race condition in your program or not, run your program → open your cmd prompt → Go to your src folder of your project and run command → go run -race main.go
It will give the status whether there is race in your program or not.
main.go
package main
import (
 "fmt"
 "sync"
)
var w sync.WaitGroup
var cnt int
func increment(s string) {
 for i := 1; i <= 10; i++ {
 x := cnt
 x++
 cnt = x
 fmt.Println(s, i, "Counter:", cnt)
 }
 defer w.Done()
}
func main() {
 fmt.Println("Starting")
 w.Add(2)
 go increment("show:") /*Both goroutines accessing increment( ) */
 go increment("display:")
 w.Wait()
 fmt.Println("Final Counter:", cnt)
}
GoLang Concurrency
Note: I’ve run this program through command prompt.

Mutex –

Mutex stands for Mutual Exclusion. Mutex is used for achieving synchronization in Golang and for accessing data safely for multiple goroutines.
Package sync provides this synchronization primitive but higher level synchronization is always better with channels.
Go provides mutual exclusion with this method
type Mutex
 → func (m *Mutex) Lock ( )
 → func (m *Mutex) Unlock( )

 main.go
package main
import (
 "fmt"
 "sync"
 "time"
)
var w sync.WaitGroup
var cnt int
var mutex sync.Mutex //create mutex type variable
func increment(s string) {
 for i := 1; i <= 10; i++ {
 time.Sleep(3 * time.Millisecond)
 mutex.Lock() //Locking while incrementing
 x := cnt
 x++
 cnt = x
 fmt.Println(s, i, "Counter:", cnt)
 mutex.Unlock() //Unlocking
 }
 defer w.Done()
}
func main() {
 fmt.Println("Starting")
 w.Add(2)
 go increment("show:")
 go increment("display:")
 w.Wait()
 fmt.Println("Final Counter:", cnt)
}
GoLang Concurrency
Now observe the output with race detector as there is no any race condition and counter values for display and show are not mixing.

Atomicity –

Don’t communicate by sharing memory; share memory by communicating is the main proverb of Golang.
Atomicity is like mutex for managing state of user. From Go 1.4 ,there is another library offered by Go for achieving thread safety in sync/atomic and has been providing low level primitives.
It provides thread safe and lock free way.
main.go
package main
import (
 "fmt"
 "math/rand"
 "sync"
 "sync/atomic"
 "time"
)
var wg sync.WaitGroup
var counter int64
func main() {
 wg.Add(2)
 go increment("show:")
 go increment("display:")
 wg.Wait()
 fmt.Println("Final Counter:", counter)
}
func increment(s string) {
 for i := 0; i < 20; i++ {
 time.Sleep(time.Duration(rand.Intn(3)) * time.Millisecond)
 atomic.AddInt64(&counter, 1)
 fmt.Println(s, i, "Counter:", atomic.LoadInt64(&counter)) 
/* access without race */
 }
 defer wg.Done()
}
GoLang Concurrency
Here, counter value is showing atomic and not affected by these two goroutines.

Channel –

Channels are like pipes that connects concurrent goroutines and passes the data through it. It is a way of sending and receiving value from one goroutine to another goroutine in FIFO manner.
While working with thread based programming,the shared variables need to protect as they might behave differently and gives wrong results. Also in threading we need to place locks, avoid deadlocks and serialization of data.
Channels provides higher level synchronization as they do not shares the data. They allows only one channel to access the data even if we are passing.
In order to create a channel, we need to use the make( ) which we have already seen while creating maps and slices. A channel is just created for passing specific type.
Example –
ch:=make(chan type,buffer_size)
ch:=make(chan int,2) where chan is variable and this will pass only integer goroutines and 1 specifying that our channel has 1 value to pass.This is known as buffered channel.
main.go 
 package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int)
go func() {
 for i := 0; i < 10; i++ {
 fmt.Println("Receiving to Channel", i)
 c <- i
 }
 }()

 go func() {
 for {
 fmt.Println()
 fmt.Println("Sending from Channel", <-c)
 }
close(c) //close channel
 }()

 time.Sleep(time.Second)
}
In this program, we have created integer channel that can pass only integer values from one goroutine to another goroutine and the functions are anonymous goroutines.
Here, when channel receives c<-i values it stop still something takes values off from channel. After taking out this values again channel will proceed further and like this way it will passes the values.
By default, sends and receives block until other side is ready. This provides guaranteed synchronization without locks and conditions.
Channel_name <- value //sends value to channel
value:=<-channel_name //receives from channel
 //Data flows in arrow direction
Channel Internal Structure contains three queues –
  • Receiving goroutine queue→ This queue is also linked list without size limit. The receiving channel information is stored in this queue along with goroutine.
  • Sending goroutine queue→ This queue is also linked list without size limit. The sending channel information is stored in this queue along with goroutine.
  • The value of buffer queue→ This is a circular queue acting as a buffer. We need to specify its capacity while creating a channel. If the values in the buffer reach its capacity then the channel is called full. If values are there then the channel is called empty.
Channel operations are:
  • len(ch) → Current no. of values in the buffer
  • cap(ch) → buffer capacity of channel
  • close(ch) → close the channel when no longer it is needed.Closing nil or already closed channel will give panic error
  • Send value to channel by using c<-value. Depending on status of channel, sending g operation may succeed to send a value to the channel, block the sending goroutine, or make the sending goroutine panic.
  • Receive (and take out) a value, from the channel by using the form value,ok=<-ch where the second value ok is optional, it reports whether the first received value,v, was sent before the channel was closed. Depending on the status of the channel, a receiving operation may succeed to receive a value from the channel or block the receiving goroutine. A receiving operation will never make the receiving goroutine panic.

For Range on channel:

For range syntax used from channels. The loop iteratively receives value from channel until it gets closed and no more values stored in buffer.
In map,slice,array it needs two iteration variable but for channel most one iteration variable is needed.
for a=range channel { is equivalent to for { 
 //use v a,ok=<-channel
 } if !ok{
 break
 }
 //use v
 }
Select – Case operations are also there to perform on channel.

Channel Rules –

GoLang Concurrency

Buffered Channels –

Channels can be buffer. We need to just provide the length as 2nd argument to make for initializing with size.
ch:=make(chan int,100)
A sender needs to close the channel to indicate that channel is no more going to receive the values.
N-to-1 : Many functions writing to same channel.
main.go
 package main
import (
 "fmt"
 "sync"
)
var w sync.WaitGroup
func main() {
 c := make(chan int)
 w.Add(2)
 go func() {
 for i := 0; i < 10; i++ {
 c <- i
 }
 w.Done()
 }()

 go func() {
 for i := 0; i < 10; i++ {
 c <- i
 }
 w.Done()
 }()
 go func() {
 w.Wait()
 close(c)
 fmt.Println("Channel closed")
 }()
 for n := range c {
 fmt.Println(n)
 }
}
Here in this program, two goroutines are writing to the same channel and ‘w’ Waitgroup is shared.
Semaphore: Semaphore is variable that can change depending on programmer defined condition.This variable is then uses a condition to control access to some system resources. (like sending messages by holding flags in certain condition).
main.go
package main
import (
 "fmt"
)
func main() {

 c := make(chan int)
 ok := make(chan bool)
 go func() {
 for i := 1; i <= 10; i++ {
 c <- i
 }
 ok <- true
 }()
 go func() {
 for i := 11; i <= 20; i++ {
 c <- i
 }
 ok <- true
 }()
 go func() {
 <-ok //wait till ‘true’ comes
 <-ok
 close(c) /* if you put this code outside of goroutine 
 output will display blank screen 
 }() 
 for n := range c { //receiving from channel
 fmt.Println(n)
 }
}
In the above example, we have created channel one is int and another one is bool. So like instead of doing waitgroup w.Done() we are putting true on channel c.
1-to-N: One channel writing to many functions
main.go
 package main
import (
 "fmt"
)
func main() {
 nums := 10
 c := make(chan int)
 ok := make(chan bool)

 go func() {
 for i := 0; i < 10; i++ {
 c <- i
 }
 close(c)
 }()
 for i := 0; i < nums; i++ { //writing to 10 functions at a times
 go func() {
 for n := range c {
 fmt.Println(n)
 }
 ok <- true
 }()
 }
 for i := 0; i < nums; i++ {
 <-ok
 }
}
Here channel is passing data to 10 channels.
Pass return channels : we can pass channel to functions and also can return the channel.
main.go
package main
import "fmt"
func main() {
 c := send()
 cSum := receive(c)
 for n := range cSum {
 fmt.Println(n)
 }
}
func send() chan int { //channel as return type
 out := make(chan int)
 go func() {
 for i := 0; i < 10; i++ {
 out <- i
 }
 close(out)
 }()
 return out
}
func receive(c chan int) chan int { //channel as arg and return type
 out := make(chan int)
 go func() {
 var sum int
 for n := range c {
 sum += n
 }
 out <- sum
 close(out)
 }()
 return out
}

Channel Direction:

While using channels as function parameters, you can specify the channel meant for use whether to only send or receive of for both. If channel is not represented by any direction that means channel is bidirectional i.e. it can send as well as receive values.
package main
import "fmt"
func main() {
 c := increment()
 cSum := pull(c)
 for n := range cSum {
 fmt.Println(n)
 }
}
func increment() <-chan int { //receive only channel
 out := make(chan int)
 go func() {
 for i := 0; i < 10; i++ {
 out <- i
 }
 close(out)
 }()
 return out
}
func pull(c <-chan int) <-chan int {
 out := make(chan int)
 go func() {
 var sum int
 for n := range c {
 sum += n
 }
 out <- sum
 close(out)
 }()
 return out
}
Well, this was all about ‘Concurrency’. In our next chapter, we will be focusing on ‘Error Handling’ in Golang. Make sure you check it out.

subscribe to our newsletter

   
   
Related Posts

Leave a Comment

goland-data-structuresgo-with-orm