Goroutine vs Threads in Golang [8 Differences]


GO

In this article, we will be discussing the key difference between Goroutine and Thread in Golang. We have already covered goroutines in a separate article so we will just concentrate on the comparison of goroutines with threads.

 

Process vs Thread vs Goroutine

A process is an execution environment that contains instructions, user data, and system data parts, as well as other types of resources that are obtained during runtime, whereas a program is a file that contains instructions and data that are used for initializing the instruction and user-data parts of a process.

A thread is a smaller and lighter entity than a process or a program. Threads are created by processes and have their own flow of control and stack. A quick and simplistic way to differentiate a thread from a process is to consider a process as the running binary file and a thread as a subset of a process.

A goroutine is the minimum Go entity that can be executed concurrently. The use of the word "minimum" is very important here, as goroutines are not autonomous entities like UNIX processes – goroutines live in UNIX threads that live in UNIX processes. The main advantage of goroutines is that they are extremely lightweight and running thousands or hundreds of thousands of them on a single machine is not a problem.

You can read more at CPU, processors, core, threads - Explained in layman's terms

 

How OS Thread works?

  • OS threads are scheduled by the OS kernel.
  • Every few milliseconds, a hardware timer interrupts the processor, which causes a kernel function called the scheduler to be invoked. This function suspends the currently executing thread and saves its registers in memory, looks over the list of threads and decides which one should run next, restores that thread’s registers from memory, then resumes the execution of that thread.
  • Because OS threads are scheduled by the kernel, passing control from one thread to another requires a full context switch, that is, saving the state of one user thread to memory, restoring the state of another, and updating the scheduler’s data structures.
  • This operation is slow, due to its poor locality and the number of memory accesses required, and has historically only gotten worse as the number of CPU cycles required to access memory has increased.

 

How goroutine scheduling works?

  • The Go runtime contains its own scheduler that uses a technique known as m:n scheduling, where m goroutines are executed using n operating system threads using multiplexing
  • The job of the Go scheduler is analogous to that of the kernel scheduler, but it is concerned only with the goroutines of a single Go program.
  • Unlike the operating system’s thread scheduler, the Go scheduler is not invoked periodically by a hardware timer, but implicitly by certain Go language constructs.
  • For example, when a goroutine calls time.Sleep or blocks in a channel or mutex operation, the scheduler puts it to sleep and runs another goroutine until it is time to wake the first one up. Because it doesn’t need a switch to kernel context, rescheduling a goroutine is much cheaper than rescheduling a thread.

 

Understanding GOMAXPROCS with Example

The Go scheduler uses a parameter called GOMAXPROCS to determine how many OS threads may be actively executing Go code simultaneously. Its default value is the number of CPUs on the machine, so on a machine with 8 CPUs, the scheduler will schedule Go code on up to 8 OS threads at once. (GOMAXPROCS is the n in m:n scheduling.) Goroutines that are sleeping or blocked in a communication do not need a thread at all. Goroutines that are blocked in I/O or other system calls or are calling non-Go functions, do need an OS thread, but GOMAXPROCS need not account for them.

 

Get value of GOMAXPROCS

To programmatically discover the GOMAXPROCS value, you can use following code:

package main

import (
	"fmt"
	"runtime"
)

func getGOMAXPROCS() int {
	return runtime.GOMAXPROCS(0)
}

func main() {
	fmt.Printf("GOMAXPROCS: %d\n", getGOMAXPROCS())
}

Output:

$ go run main.go 
GOMAXPROCS: 4

 

Example-1

Let's take this simple go code to test the functionality of GOMAXPROCS which prints 0 and 1:

package main

import (
	"fmt"
)

func main() {

	for {
		go fmt.Print(0)
		fmt.Print(1)
	}
}

Output:

# GOMAXPROCS=1 go run main.go 
111111111111111111111111111111111111111111111111....00000000000000000000000000000000

# GOMAXPROCS=2 go run main.go 
11111111100111111100001111111111100000000011000000000000000111111111111111111

Explanation:

In the first run, at most one goroutine was executed at a time. Initially, it was the main goroutine, which prints ones (output trimmed using ...). After a period of time, the Go scheduler put it to sleep and woke up the goroutine that prints zeros, giving it a turn to run on the OS thread.

In the second run, there were two OS threads available, so both goroutines were trying to utilise the thread simultaneously, printing digits at some interval.

In your case the output may differ depending on various factors such as environment, goroutine changes etc.

 

Example-2

Let's take another example, here we will use a small shell script to toggle the GOMAXPROCS value and then perform a build operation on one of our packages:

#!/bin/bash

for i in 1 2 3 4
do
    export GOMAXPROCS=$i
    printf "\nBuild with GOMAXPROCS=$i:"
    time go build -a global-vars
done

Output:

# sh gomaxprocs.sh 

Build with GOMAXPROCS=1:
real    0m9.005s
user    0m7.993s
sys     0m1.141s

Build with GOMAXPROCS=2:
real    0m4.488s
user    0m8.572s
sys     0m1.227s

Build with GOMAXPROCS=3:
real    0m4.017s
user    0m8.816s
sys     0m1.373s

Build with GOMAXPROCS=4:
real    0m3.866s
user    0m9.073s
sys     0m1.357s

Explanation:

We have a local package global-vars which we are trying to build using different GOMAXPROCS value.

Here you can see, as we increase the number of GOMAXPROCS value, we see improvement in the build time of the go binary.

 

Major difference between Goroutine and Thread in Golang

Difference:-1 Scheduling management

In Golang, Goroutines are executed by the Go runtime program which runs in user space above the kernel known as the Go scheduler. Goroutines are cooperative schedulers because there is a well-defined user-space event that happens at a safe point in the code to make a scheduling decision. While Thread is considered to be an operating system scheduler which is the preemptive scheduler. Thus you can't predict what is being executed by the scheduler rather the kernel is making decisions and the program is non-deterministic

 

Difference:-2 Communication medium used

In Golang, Goroutines enhance communication through the use of a channel and sync package which provides a wait group function. while Thread has not established clearly the communication means, in multiple threads executing communication is made through memory location.

 

Difference:-3 Execution speed

Goroutine have a faster execution speed while Thread have a slower execution speed.

 

Difference:-4 Infrastructure dependency

Infrastructure stands for the operating system and hardware resources. In Golang, Goroutine is executed independently to any infrastructure since it's a method or function being executed. while Thread dependents on kind of infrastructure in place.

 

Difference:-5 stack management

In Golang, Goroutine is executed in a stack of 2kb (kilobytes), which grows gradually and is destroyed once execution is done/completed. while Thread It's also executed in a stack, however, it requires at least a minimum of one megabyte to execute, and stack size is fixed thus threads do not have to grow an able segment of the stack. thus ease stack management with goroutines as compared with threads.

 

Difference:- 6 Latency during program execution

Goroutines easily communicate with each other through channels, thus low latency is experienced from one channel to another. while in Thread Since there is no communication medium between one thread to another then communication takes place with high latency.

 

Difference:- 7 Resource control

In Golang, Goroutine function or methods are controlled by Go runtime while in Thread resources are controlled by operating system.

 

Difference:- 8 Local storage management

In Go, Goroutine does not have a unique Identity of resources, and threads are not stored in local storage as compared to Thread which provides a unique resource identification and all threads have local storage.

 

Summary

In Golang, Goroutine works with go channels to deliver concurrency. Channels act as a pipeline between goroutines. This feature makes the Go language so powerful in executing multiple programs, thus many cloud computing programs will be written in Golang in near future. The main drawback in Thread is the complexity in implementation to achieve the concurrency mechanism used in Goroutines.

 

Reference

Goroutine
goroutines

 

Deepak Prasad

Deepak Prasad

He is the founder of GoLinuxCloud and brings over a decade of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels in various domains, from development to DevOps, Networking, and Security, ensuring robust and efficient solutions for diverse projects. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment