The Go programming language has built-in support for concurrent programming. The basic primitive type for this support is provided by goroutines, which are similar to lightweight threads that execute in the background. Unlike threads, which are supported by many programming languages, goroutines are cheaper in terms of memory footprint, inter-thread communication, low latency, and they also have a faster startup time. In short, what Java or other programming languages call threads, Go calls goroutines (they are a more efficient version of a thread).
What is a Thread in Computer Programming?
A running program is technically known as a process or, rather, a sequential execution of an instruction set. This simply means one instruction or code statement executes after another, and then the next, and so on. This process has a defined beginning and an end, which can occur at any given point during the runtime of the program. There is a single point of execution in the sequence.
A single thread is similar to a process and also has a beginning, a sequence, and an end. But unlike a process, a thread itself is not a program in itself; this is because a thread cannot run on its own. Instead, threads pick a subpart of the large sequence and executes it as a separate identity. The real benefit of a thread is not in the execution of its sequential subpart, but rather that there can be multiple such subparts (or threads) running at the same time, each with its own task to perform different operations in a single program.
A simple example of multiple threads – or multithreading – might be a web browser where one can download a file, scroll through pages, print in the background, and so forth, all at the same time or concurrently. Technically, a thread is called a lightweight process because of its lower memory footprint than a process (in a multiprocessing environment). Be it a process or a thread in a multiprocessing or multithreading environment, Linux typically generalizes them by calling them a task (which can either be a process or a thread).
In Linux/Unix, when the system call fork() is invoked, a new task (a process) is created/duplicated. Similarly, to create a POSIX thread, pthread_create() is called. One important difference between a process and a thread is that processes do not share file descriptors, PIDs, or memory space among their peers; however, threads share all of them. This is the basic difference between a thread and a process.
Reading: Getting Started with Go Programming
What is a go routine?
Goroutines are a further refinement of the concept of a thread. The refinement was so remarkable that it became a separate entity called a “goroutine” instead of a thread. In a multi-processing environment, the creation, and maintenance of a process is heavily dependent on the underlying operating system. Processes consume operating system resources and do not share them among peers. Threads, although lighter than a process due to resource sharing among peer threads, need a large stack size – almost 1 MB in size. Therefore, N number of threads means N x 1 MB, which is considerably large.
Moreover, the switching of threads requires the restoration of registers, such as program counters, stack pointers, floating-point registers, and so forth. Because of this, the maintenance cost of a process or a thread is actually pretty high. Also, in cases where data is shared among peers, there is an overhead of synchronization for the data – not only in shared memory but also in different memory pools, like L1 cache, which helps when running tasks on multiple cores. Although the overhead switching between tasks has been improved a lot, creating new tasks still results in overhead. Due to this, they sometimes take a heavy toll on application performance, even though threads are designated as lightweight.
The advantage of goroutines is that they are not dependent on the underlying operating system; rather, they exist in the virtual space of the Go runtime. As a result, any optimization on a goroutine has a lower dependency on the platform it is running. Goroutines begin with the initial capacity of only a 2KB stack size, which is considerably low. Go routines, along with channels, support communicating sequential process (CSP) models of competition, where values are passed between independent activities. These activities are – you may have guessed it – called goroutines. However, the variables, for the most part, are confined to a single activity.
Reading: Understanding Functions in Go
How to create a Go routine in Go
Developers should understand that goroutines are only quantitatively superior to threads. Qualitatively, they are the same. As a program gets executed in Go, the first goroutine is the one that calls the main function. Programmers can also refer to this as the main goroutine. Later, if we want to create new goroutines, we can use the go statement. For example, a simple function call can be created using the following Go example code:
myfunc()
This causes the function to be called and return to the point of its call. Now, if we write:
go myfync()
The prefix go statement causes the function to be called in a new goroutine.
The Go runtime, which runs in the background, starts with a set of goroutines, a scheduler, and user code. It then creates an OS thread. This OS thread handles all the goroutines, and the maximum is defined by GOMAXPROCS. From the Godocumentation: ”The GOMAXPROCS variable limits the number of operating system threads that can execute user-level Go code simultaneously. There is no limit to the number of threads that can be blocked in system calls on behalf of Go code; those do not count against the GOMAXPROCS limit. This package’s GOMAXPROCS function queries and changes the limit.”
Here is a simple example:
func main() { ivar := 10 go fmt.Printf(“1. The value in ivar is %dn”, ivar) ivar++ go fmt.Printf(“2. The value in ivar is %dn”, ivar) go func() { ivar++ go fmt.Printf(“3. The value in ivar is %dn”, ivar) }() ivar++ go fmt.Printf(“4. The value in ivar is %dn “, ivar) time.Sleep(1000000) }
Running the above code in your IDE or code editor will result in the following output:
4. The value in ivar is 12 3. The value in ivar is 13 1. The value in ivar is 10 2. The value in ivar is 11
The following is the same code, without using a goroutine:
func main() { ivar := 10 fmt.Printf(“1. The value in ivar is %dn”, ivar) ivar++ fmt.Printf(“2. The value in ivar is %dn”, ivar) func() { ivar++ fmt.Printf(“3. The value in ivar is %dn”, ivar) }() ivar++ fmt.Printf(“4. The value in ivar is %dn”, ivar) time .Sleep(1000000) }
Here, the expected output would be:
1. The value in ivar is 10 2. The value in ivar is 11 3. The value in ivar is 12 4. The value in ivar is 13
As we prefix any function or statement with the keyword go, it creates a new goroutine with the call frame and schedules it to run. The idea and behavior are quite synonymous with threads and have full access to the arguments, globals, and anything reachable. Also, we can write goroutines with anonymous functions; if you remove the sleep() call in the first example, the output will not be shown. Therefore, the function call at the end of the main causes the main goroutine to stop and exit prior to the spawned goroutine having any chance to produce any output. However, it is not necessary to wait for all the goroutines to exit prior to the program’s termination.
Observed in the second example (which excluded our goroutine), the output is pretty straightforward and is in sequence. Now compare it with the output of the first example (which included our goroutine) and we can see there is no sequence; this is because concurrent execution does not guarantee sequential execution. The compiler does provide constraints on the ordering of memory access in case of concurrent execution of goroutines. Therefore, it is not predictable which line is written after the goroutine will be executed in which order.
Reading: Methods in Go Explained
Final Thoughts on Goroutines in Golang
In this Go programming tutorial, we have tried to give a glimpse of what goroutines are all about. Discussion of goroutines is incomplete without having any idea of what channels in Go are. With that in mind, we invite you to check out our follow-up article on working with channels in Go.
read more Go and Golang programming tutorials.