Concurrency
Goroutines
A goroutine
is a lightweight thread managed by the Go runtime.
It allows a function in Go to run concurrently or in parallel with other functions.
They don't support return
values, any sort of communication to the outside must be done through channels
.
Concurrency and Parallelism
Goroutine vs OS thread
Go doesn't create OS threads for each goroutine.
Instead, Go runs thousands of goroutines on a small number of OS threads (Pool of OS threads).
Go Runtime Scheduler dynamically assigns goroutines to available OS threads.
It does it by multiplexing the goroutines, so if a goroutine blocks (e.g wating for I/O), Go moves another goroutine onto the OS thread, ensuring efficient CPU utilization. (Just like an OS does with OS threads in logical CPU cores)
Too many goroutines can lead to memory pressure and context switching overhead.
Memory Usage
~2 Kb
per Goroutine
~1 Mb
per thread
Scheduling
Cooperative, managed by Go
Preemptive, managed by OS
Speed
Fast to start, low overhead
Slow to start, heavy overhead
Communication
Use channels (safe)
Use locks/mutexes (error-prone)
Monitoring and Profiling
Use pprof
, Go's profiling tool to analyse CPU usage and goroutines blocking times.
Best practices
Goroutines inside loops
Goroutine leaks
A goroutine leak happens when it never exits, causing memory issues.
Always have a receiver for every channel send operation.
Use sync.WaitGroup
to wait for multiple Goroutines
sync.WaitGroup
to wait for multiple GoroutinesDon't use time.Sleep()
, use sync.WaitGroup
to wait for multiple goroutines.
Limit Goroutines with Worker Pools
Worker Pools prevent flooding your app with goroutines.
Too many goroutines can overload memory. Use worker pools to limit the number of concurrent goroutines.
Use GOMAXPROCS with container CPU limits
If limiting the Docker container's or Kubernetes Pod's CPU
amount, make sure to also provide the same number to Go Scheduler.
Failing to do so can increase to likelihood of CPU throttling.
Starting a Goroutine
Channels
It is simply a value that can be used as a communication device, to transmit some kind of data.
In this example, the channel holded the main program until the goroutine ended, because when it finished it emitted a value to the channel.
Waiting for multiple goroutines
In the example bellow, to wait for both goroutines, you would have to listen two times on the channel.
But this is not very scalable.
A solution is would be to have a Slice of channels, and then listen to them in a loop.
Handle errors in Channels
To handle this use the select
control structure to either read from errorChan
if there were any errors, or from doneChan
if all wen't good.
The select
control structure, will run code from the channel case
that emmited the value first, and will not care about handling the other cases after.
To handle multiple channel Slices.
Defer
A keyword defer
that indicates to Go that a called function should execute only when the surronding function or method finishes.
In this example, after the file was opened you may state to close it with defer
. So when the handleFile
function exits, the file.Close()
will be called.
Last updated