Introduction to Concurrency in Go

With the rise of multi-core processors, inherent support for concurrency paradigms by programming languages has been on the rise. Go programming language has inherent support for CSP(communication sequential processes) and shared memory multithreading.

Concurrency models:

The CSP model is similar to the actor model where multiple simultaneously running actors(processes/threads) communicate between themselves using the concept of message passing. There are subtle differences between the CSP model and the actor model, one of the main differences being: In the actor model, actors have peer to peer direct communication within themselves, but in the CSP model, processes communicate between each other using channels, and hence there is no direct communication allowed between processes. I will discuss more on channels in my later publications. The support for independently running actors/processes/threads makes the system loosely-coupled and hence more fault tolerant i.e. incase of any failure/error in any particular actor/process/thread, the fault can be easily localized & hence it does not result in the failure of the whole system making the system fault-tolerant.

Shared memory multithreading is a more traditional model which deals with the notion of mutex, memory synchronization, lazy initialization..etc. I will be discussing more on this in my upcoming publications.

Goroutines:

In Go, each simultaneously executing activity/function is called a goroutine. Messages in the form of values and variables may be passed or exchanges between goroutines using channels.

New goroutines can be created by using the keyword go before the function or method call.

f()   // synchronously call f(); wait to return; 
go f() // create a new goroutine that calls f(); dont wait;
  • It is important to note that goroutines are called in an asynchronous way i.e. the main control does not stay with the goroutine till the return statement is executed. It follows a sort of a fire & forget approach.
  • When the main program returns all running goroutines are terminated even if they have not completed execution and the program exists.

The following example of an clock server program in Go illustrates how easily a sequential clock server can be made into a concurrent one.

//Sequential Clock Server
package main
import (
“io”
“log”
“net”
“time”
)
func main() {
listener, err := net.Listen(“tcp”,”localhost: 8000")
if err != nil {
log.Fatal(err)
}
for {
conn,err := listener.Accept()
if err != nil {
log.Print(err)
continue
}
handleConn(conn)
}
}
func handleConn(c net.Conn) {
defer c.Close()
for {
_, err := io.WriteString(c, time.Now().Format(“15:04:05\n”))
if err != nil {
log.Print(“Client Disconnected!”)
return
}
time.Sleep(1 * time.Second)
}
}

The above go program is an sequential clock server that continuously returns the current time to the connected client, once per second. Lets name the above go program as sequentialClockServer.go .We can build and run the program using the following commands:

$ go build sequentialClockServer.go
$ ./sequentialClockServer

Now, we have to access the server, either by writing our own tcp client program or by using any of the built-in tools. Lets use the built-in tool “netcat” for this program:

$ nc localhost 8000
18:51:29
18:51:30
18:51:31
18:51:32
^C
  • It is important to note that the server program must be running when the nc utility is used. “Ctrl-c” can be used to stop the client.
  • Due to the sequential nature of the server, while one nc client is running, if another client is connected then no response is obtained from the server till the first client is disconnected.

The above sequential clock server can be made concurrent very easily. The following changes are required in the sequentialClockServer.go program:

for {
conn,err := listener.Accept()
if err != nil {
log.Print(err)
continue
}
go handleConn(conn)
}

The only change that is required is the addition the the go keyword as a prefix before calling the handleConn function. When we add the go prefix before a function, it is executed as a goroutine.

  • Now,we can run the server and connect multiple clients using the nc utility tool as shown above. Multiple clients can be connected by opening multiple tabs and executing the “nc localhost 8000” command in all the terminal tabs.
  • You will see that all the clients are simultaneously getting responses from the server, as the server is now concurrent in nature due to the use of goroutines.
  • While we call goroutines it is important to consider, if it is safe to call those functions concurrently. I will discuss more on concurrency safety in my later posts.

In this post, I have explained about a bit about the CSP & actor models and have given a introduction into goroutines using simple examples. The code used in this post is also available in github. Any code used in any of my medium articles would be available in the following github repository. In my next post, I will be discussing more on channels and how we can use them to communicate between goroutines.

References:

  • The Go Programming Language, Alan A.A. Donovan & Brian W. Kernighan
  • A Tale of Two Concurrency Models by David Chisnall
  • https://en.wikipedia.org/wiki/Communicating_sequential_processes