Golang Concurrency

Golang Concurrency

golang concurrency

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 a number of requests made from the browser and returns the responses. Every request is like a small program that is handled.

It is always best to run the small components at the same time (like web server handles the multiple requests at a same time). So, working on the multiple programs simultaneously is known as concurrency.

concurrency

 

If you observe in the 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 a 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.

Goroutines()

 

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 the same time it will call Wait to block and wait until all goroutines finish their execution.

To use sync.WaitGroup :

  • Create the 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

waitgroup()

 

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 the main goroutine also have to call Wait( ) on that wait group 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.

concurrency & parallelism

 

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 try to access the resource like variable or data structure and attempt to read or write the resources without regard to another routine. So it will create tremendous problems.

So Golang tooling introduced race detector. Race detector is code that is built in your program during the build process. Once your program starts, it will start to detect race condition. It is a really superb tool and does a great job.

For detecting whether there is a 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 a race in your program or not.

race condition
command

 

Note: I’ve run this program through command prompt.

Mutex –

Mutex stands for Mutual Exclusion. The 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

Mutex
Mutex

 

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 the mutex for managing the state of a 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 locks freeway.

atomicity
Atomicity

 

Here, the 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 provide higher-level synchronization as they do not share the data. They allow 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 a buffered channel.

channel

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 stops still something takes values off from a channel. After taking out this values again channel will proceed further and like this way, it will pass the values.

By default, sends and receives block until another 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 circular queue acting as a buffer. We need to specify its capacity while creating a channel. If the values in the buffer reach to 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 a panic error

  • Send value to channel by using c<-value. Depending on the status of the 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 the channel:

For range syntax used from channels. The loop iteratively receives value from channel until it gets closed and no more values stored in a buffer.

In the map, slice, array it needs two iteration variable but for channel most, one iteration variable is needed.

For range of channel

 

Select-Case operations are also there to perform on a channel.

Channel Rules –

channel rules

 

Buffered Channels –

Channels can be buffer. We need to just provide the length as the 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 the same channel.

buffered channel

 

Here in this program, two goroutines are writing to the same channel and ‘w’ Waitgroup is shared.

Semaphore: Semaphore is a variable that can change depending on programmer defined condition. This variable was then used as a condition to control access to some system resources. (like sending messages by holding flags in a certain condition).

semaphore

 

In the above example, we have created channel one is int and another one is bool. So like instead of doing wait group w.Done() we are putting true on channel c.

1-to-N: One channel writing to many functions

channel writing functions

Here channel is passing data to 10 channels.

Pass return channels: we can pass channel to functions and also can return the channel.

return channels

 

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 a channel is not represented by any direction that means the channel is bi-directional i.e. it can send as well as receive values.

channel direction

 

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.

Comments are closed.