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