Goroutines and Channels in Go
Goroutines
A goroutine is a lightweight thread of execution managed by Go's runtime. It enables concurrent programming by allowing multiple functions to run independently and simultaneously.
Goroutines are much lighter than traditional threads.
They share the same address space, making them efficient.
Goroutines are started using the
go
keyword followed by a function call.
Lightweight
Golang’s goroutines are lightweight because they are designed to be more efficient than traditional operating system (OS) threads. This efficiency comes from several optimizations at the runtime level that allow millions of goroutines to run concurrently with minimal overhead.
Start with a small stack (about
2 KB
)OS threads typically start with
2 MB
.This smaller stack means less memory is allocated upfront.
Managed by the Go runtime scheduler in user space, not by the operating system.
Go uses an M:N scheduling model, meaning M goroutines are multiplexed onto N OS threads.
The scheduler decides when to pause and resume goroutines.
Faster context switching.
Because goroutine is user level, only a few registers and the stack pointer need to be saved.
OS Threads require full CPU context to switch which make it slower.
Efficient communication using channels.
Because golang use garbage collector there is a brief pause for GC to do full scanning. For some cases this brief pause will create big impact if we running low latency application with high traffic.
Example
The printMessage function is executed concurrently by multiple goroutines.
The main function itself runs as a goroutine.
Channels
A channel is a mechanism that goroutines use to communicate with each other and synchronize their execution. Channels enable the safe exchange of data between goroutines without the need for explicit locking.
Channels are typed; you define the type of data they transmit.
Use the
make
function to create a channel.Use
<-
for sending (channel <- value
) and receiving (value := <-channel
) data.Channels can be unbuffered (synchronous) or buffered (asynchronous).
Example
The sendMessage goroutine sends data to the channel.
The main function waits to receive the data before proceeding.
Interaction Between Goroutines and Channels
Sending Data: One goroutine can send data to a channel.
Receiving Data: Another goroutine can block until it receives the data.
Synchronization: Unbuffered channels act as synchronization points, ensuring one goroutine waits for the other.
Example
Two goroutines calculate partial sums concurrently.
Results are sent back through the result channel.
The main function waits to receive both
results
before combining them.
Buffered vs. Unbuffered Channels
Unbuffered Channels
Default type.
Sender blocks until a receiver is ready.
Receiver blocks until a sender sends data.
Ensures synchronization.
Buffered Channels
Allow multiple values to be queued in the channel.
Sender only blocks when the buffer is full.
Receiver only blocks when the buffer is empty.
Channel to Prevent Race Condition
Lets create a simplified example of data race in Golang. In here we want increase a counter
variable, but this variable will be accessed by many go routines.
As we can see above that this program returning incorrect counter
value. This is because each goroutine modifies counter
without synchronization, leading to data races and an incorrect value.
This can be fixed by using channel.
By adding channel we make sure that only one goroutine (receiver) update the counter. This will ensure safe access to the counter
variable and return consistent correct value. This method is inline with Golangs's slogan below.
Do not communicate by sharing memory; instead, share memory by communicating.
Please do note that the example above is oversimplified. In the real word scenario you should use sync.WaitGroup
to wait all the goroutine to finish instead os using time.Sleep
.
Summary
Goroutines provide concurrency, while channels facilitate communication and synchronization.
Channels prevent race conditions by allowing data to flow safely between goroutines.
Use unbuffered channels for synchronization and buffered channels for decoupled communication.
References
Last updated