Wanna break from your daily routine ? Ready ? Go !
Golang introduction : go routines and channels
The go language (or Golang) was created by Google in order to make development easier and more accessible. It’s a strongly typed, compiled open source language.
One of the main advantages is the intrinsic management of concurrency achieved thanks to specific features I am going to detail in this article : routines and channels.
Go routines are functions or methods that will be executed in concurrence with each other.
Before going further, let me make a brief aside to explain the difference between concurrency and parallelism. Actions executed in parallel are independent from each other while if they are executed in concurrence they might need the same resources at the same time. We therefore need to manage the resource sharing in an optimal manner.
Go routines can be compared to light threads. Routine creation is less expensive than classic threads and Golang was built to withstand thousands of routines at the same time. In order to launch a go routine you just need to call the method you want to execute with the go keyword before.
Here is an example use case with a hello() method which prints « Hello ! » in the terminal and that we are going to launch in a go routine.
Output :
Note : When a go routine is launched, the program will immediately proceed to the next line without waiting for the routine execution. That’s why we need to add a sleeping time on line 10 : « time.Sleep(1*time.Second) » so we can leave time for the routine to execute the printing commands.
Note 2 : The main() function corresponds to the main routine and it must be running for all the other routines called inside to be executed. Therefore, at the end of the main function, the hello() routine will stop automatically.
Of course routines can access shared resources but Golang encourages message exchanges through go channels. Go channels are like pipes allowing to send and receive data between different go routines.
Channels are declared this way :
Note : Channels are typed, here only strings can be sent through the channel ch.
Note 2 : We introduce here the syntax := which corresponds to variable declaration and assignation.
Here is how to send data through a channel, for instance the word Hello :
And this is how you receive the sent data :
Note : The read/write arrow from/to a channel always points to the left. When the arrow points to the channel, we are writing data to the channel. When it comes from the channel, then we are reading data.
Note 2 : Here we retrieve the data read from the channel into a variable called data but it’s not mandatory, we can simply write « ← ch » to wait for something coming from the channel.
One very important feature to understand about channels is their blocking nature. Indeed when sending data to a channel, the program execution will be blocked until someone tries to receive the data. As a consequence the data delivery will be successful only if a go routine is trying to receive the data at the same time. If if is not the case the program will wait until the data can be sent and all the following actions will not be executed. It’s the same issue if you try to receive data from a channel and no other routine is sending data to this channel, then the program execution will be blocked. This characteristic can appear a bit tricky but it instead enables routine synchronization as I will explain a bit later.
To avoid program blocking if it is not possible to immediately consume data coming from the channel output, you can create buffered channels by adding a channel capacity as shown below :
Note : Here the declared channel is able to store 100 messages without blocking the program. These 100 messages will be piled up in arrival order (first-in first-out). Beyond those 100 messages, the routine that is sending data to the channel will be blocked until data are consumed in another routine.
Note 2 : The capacity of a non-buffered channel is 0.
Now that I have explained what is a go channel, let’s see an example of how to use channels to communicate between different go routines :
Here we create two channels chan1 and chan2 that we pass as arguments to a routine() method launched in a go routine. We write « Hello ! » in the channel chan1 from the main() function that we read and print in the routine. Then we write « Hi ! » in the channel chan2 in the go routine and we read and print it in the main() function.
Output :
Channels can also be used for an event-driven programming, for instance stopping the execution of a go routine. Let’s modify our first example to illustrate that.
We create a greet() method which prints « Hello ! How are you ? » every 200ms and a channel quitChannel that we pass as argument to the greet() method. This channel will be used to stop the routine when we want to. As described earlier, we need to listen permanently to our channel quitChannel in order to be able to send data through it because the channel is not bufferized.
To achieve this we are going to settle another go feature: the select case. It allows a go routine to wait on several communication operations. The select blocks until one of the cases can be executed. In our example, either we receive something from the quitChannel, or we print « Hello ! How are you ? ».
Output :
Since we waited 1 second before the end of the main() function, the greet() go routine had time to print « Hello ! How are you ? » five times. For now we didn’t send any data to the quit channel.
Let’s add another method reply() to which we also pass the quit channel as argument and we launch this method in another go routine. This method will print « Fine, thanks ! » and will then tell the greet() routine to stop by sending data to the quit channel.
Output:
In this example, we see that the infinite loop in the greet() method was stopped after the first « Hello ! How are you ? » printing thanks to the information sent through the quit channel in the reply() method.
Note : When several routines are launched at the same time, they are managed in an independent way according to CPU availability and the order in which routines are executed is not guaranteed. Therefore it is also likely to obtain the following result in output, even if it does not really make sense in our example :
Note 2 : Since both routines are executed almost simultaneously, the default case in the greet() routine was chosen just before receiving data from the channel quit sent from the reply() routine. That’s why we still see “Hello! How are you?” printed in output.
Note 3 : If you try to run this program in the go playground [2], you will not get the latter output because the playground is limited in CPU.
To sum up, we demonstrated that sharing resources using go channels avoids race conditions by construction. Indeed only one go routine can access the data sent through a channel at a given time, therefore there is no concurrency in resources sharing. To put things in a nutshell, « Do not communicate by sharing memory ; instead, share memory by communicating » , quoted from [3].
Last but not least, here are some useful links if you want to learn more about go features :
[1] The official Golang tutorial to quickly discover the langage main features :
[2] The go playground to easily test features without needing to install go tools on your computer :
[3] The Go Bootcamp book created after the first go bootcamp held on 2014 :
[4] A well-made blog to go deeper into go concepts :
[5] The YouTube channel JustForFunc which describes concrete examples of go usage :
I would like to thank Thibaut Gavard and Alan Lemaire, developers at Orange Business Services for their help writing this article.
Hélène Laffon, developer at Abbeal