The stenght of Go language is concurrency support, the ability to spawn multiple goroutines.

As it lets us divide our program into concurrent (“parallel”) processes. But comes with a set of new issues we have to consider, related to shared memory and synchronization.

Mutex (Mutual Exclusion)

What Mutex is.

 

This spawns 1000 goroutines incrementing the counter variable, then prints the counter value.

 

Because fmt.Println(counter) may be called before all 1000 goroutines finished.

by using a sync.WaitGroup, Println(counter) will wait for all goroutines to finish:

 

wg.Wait() will block the main goroutine until all goroutines will call wg.Done().

 

because of a Race Condition :

Multiple goroutines are accessing the counter variable simultaneously.

Since counter++ is just equivalent of

 

some goroutines will read the same value from counter and set the incremented value to be the same.

Solution

We need a mechanism that allows goroutines to get a hold of a certain “flag”, using an atomic operation , will ensure that only one goroutine can invoke the operation at a time. Whoever is holding this “flag”, will block others, until the flag will be released.

Once released (with mutex.Unlock()), the blocked goroutine will be able to acquire the “flag” and proceed.

If we’ll put a Mutex lock in front of the line counter++, we can ensure that only a single goroutine can access the counter variable at a time.

 
 

Suppose we have an expensive computation that takes a lot of time and RAM to complete:

Invoking go Expensive() a thousand times, can easily reach the memory limit. Therefore, it’s important to control the number of running goroutines at the same time.

Semaphore

 

We are spawning 10 goroutines, each takes a second to complete and we are printing the number of currently running goroutines. How many goroutines will run simultaneously at the most?

 

Without restricting the number of goroutines, we can easily reach a big number of goroutines, reaching resources limit.

 

A Semaphore is very similar to a Mutex, but with a key difference:

While a Mutex has a single “flag”, a Semaphore has a number of slots (usually represented as an integer counter). So if slots is set to 3, the next goroutine that will invoke sm.Acquire(ctx, 1) will be blocked until one of the slots will be available.

 

In this example we call the sm.Acquire function, outside of the goroutine, right from the main goroutine, which blocks the main goroutine from adding more goroutines.

We can also spawn the goroutine and block that.

Go Channels

In many cases using channels may eliminate the need for Mutex and Semaphore.

 

This program has a better design:

  • don’t need a Mutex lock
  • don’t need a WaitGroup
  • goroutines are not reading and reassigning shared memory
  • Fewer dependencies and steps

Channels have a very efficient blocking mechanism. So whenever a goroutine is trying to send to a full channel, it will be blocked until some other goroutine will read the value from the channel.

We’ll write a program that requests a bunch of web pages and sums up the number of occurrences of a word.

But instead of requesting and processing each web page synchronously, We will put every request in its own goroutine, and accumulate the results into our main goroutine.

 

using one chanel:

 

Now let’s declare 3 channels:

  • resChan to receive the sum of occurrences on each page
  • errChan to receive errors if there are any
  • sigChan to receive OS signals such as INTERRUPT (Ctrl+C)

Let’s spawn each http_get(url) in a separate goroutine, if there’s an error, we’ll send this error to errChan, if not, we’ll send the sum to resChan

 

Finally, let’s make our main goroutine listen and handle all incoming data from all channels:

  1. If data is coming from resChan, Accumulate to a global sum variable
  2. If an error is coming from errChan, Print the error
  3. If sigChan receive an INTERRUPT signal, Close all channels, print total sum, and exit with 0

This can be achieved, using a select block:

 

the complete base program: