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 are handled.
It is always best to run the small components at the same time (like web server handles the multiple requests at the 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.
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 the main goroutine exits, the program will also exit.
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 :
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 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.
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.
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 other routines. 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 of 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) }
Note: I’ve run this program through command prompt.
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) }
Now observe the output with a race detector as there is no any race condition and counter values for display and show are not mixing.
Don’t communicate by sharing memory; share memory by communicating is the main proverb of Golang projects ideas.
Atomicity is like mutex for managing 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 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() }
Here, counter value is showing atomic and not affected by these two goroutines.
Channels are like pipes that connect 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 the 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 these values again channel will proceed further and like this way, it will pass the values.
By default, sends and receives block until the 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 –
Channel operations are:
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 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 a channel.
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 the 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 }
While using channels as function parameters, you can specify the channel meant for use whether to only send or receive of for both. If the channel is not represented by any direction that means the 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.
How to Effectively Hire and Manage a Remote Team of Developers.
Download NowThe Mindbowser team's professionalism consistently impressed me. Their commitment to quality shone through in every aspect of the project. They truly went the extra mile, ensuring they understood our needs perfectly and were always willing to invest the time to...
CTO, New Day Therapeutics
I collaborated with Mindbowser for several years on a complex SaaS platform project. They took over a partially completed project and successfully transformed it into a fully functional and robust platform. Throughout the entire process, the quality of their work...
President, E.B. Carlson
Mindbowser and team are professional, talented and very responsive. They got us through a challenging situation with our IOT product successfully. They will be our go to dev team going forward.
Founder, Cascada
Amazing team to work with. Very responsive and very skilled in both front and backend engineering. Looking forward to our next project together.
Co-Founder, Emerge
The team is great to work with. Very professional, on task, and efficient.
Founder, PeriopMD
I can not express enough how pleased we are with the whole team. From the first call and meeting, they took our vision and ran with it. Communication was easy and everyone was flexible to our schedule. I’m excited to...
Founder, Seeke
Mindbowser has truly been foundational in my journey from concept to design and onto that final launch phase.
CEO, KickSnap
We had very close go live timeline and Mindbowser team got us live a month before.
CEO, BuyNow WorldWide
If you want a team of great developers, I recommend them for the next project.
Founder, Teach Reach
Mindbowser built both iOS and Android apps for Mindworks, that have stood the test of time. 5 years later they still function quite beautifully. Their team always met their objectives and I'm very happy with the end result. Thank you!
Founder, Mindworks
Mindbowser has delivered a much better quality product than our previous tech vendors. Our product is stable and passed Well Architected Framework Review from AWS.
CEO, PurpleAnt
I am happy to share that we got USD 10k in cloud credits courtesy of our friends at Mindbowser. Thank you Pravin and Ayush, this means a lot to us.
CTO, Shortlist
Mindbowser is one of the reasons that our app is successful. These guys have been a great team.
Founder & CEO, MangoMirror
Kudos for all your hard work and diligence on the Telehealth platform project. You made it possible.
CEO, ThriveHealth
Mindbowser helped us build an awesome iOS app to bring balance to people’s lives.
CEO, SMILINGMIND
They were a very responsive team! Extremely easy to communicate and work with!
Founder & CEO, TotTech
We’ve had very little-to-no hiccups at all—it’s been a really pleasurable experience.
Co-Founder, TEAM8s
Mindbowser was very helpful with explaining the development process and started quickly on the project.
Executive Director of Product Development, Innovation Lab
The greatest benefit we got from Mindbowser is the expertise. Their team has developed apps in all different industries with all types of social proofs.
Co-Founder, Vesica
Mindbowser is professional, efficient and thorough.
Consultant, XPRIZE
Very committed, they create beautiful apps and are very benevolent. They have brilliant Ideas.
Founder, S.T.A.R.S of Wellness
Mindbowser was great; they listened to us a lot and helped us hone in on the actual idea of the app. They had put together fantastic wireframes for us.
Co-Founder, Flat Earth
Ayush was responsive and paired me with the best team member possible, to complete my complex vision and project. Could not be happier.
Founder, Child Life On Call
The team from Mindbowser stayed on task, asked the right questions, and completed the required tasks in a timely fashion! Strong work team!
CEO, SDOH2Health LLC
Mindbowser was easy to work with and hit the ground running, immediately feeling like part of our team.
CEO, Stealth Startup
Mindbowser was an excellent partner in developing my fitness app. They were patient, attentive, & understood my business needs. The end product exceeded my expectations. Thrilled to share it globally.
Owner, Phalanx
Mindbowser's expertise in tech, process & mobile development made them our choice for our app. The team was dedicated to the process & delivered high-quality features on time. They also gave valuable industry advice. Highly recommend them for app development...
Co-Founder, Fox&Fork