How Go Routines work
The model is like this
G GO routines
M OS threads
P Processors(Work deque) (deques means double ended queue)
Go routines run on OS threads. The GOMAXPROC variable set the number of P (Processors) that our program can use.
By default, GOMAXPROC variable is set to the same number of Logical CPU.
So, the same amount of OS threads will run on the same number of Logical CPU.
When we set more GOMAXPROC amount than the number of Logical CPU, the runtime will spins up more OS threads.
When we got more OS threads than the Logical CPU counts, the Logical CPUs will try to run those OS threads in the same time as much as possible.
How the Logical CPU would handle over count OS thread is by stopping some first processed OS threads and switch to the OS threads that haven't been executed.
This is where the context switching occurs and it can potentially slow down the application.
- Faster for switching between goroutines (Switching between cpu threads is expensive.).
- Initial stack size for each goroutines can be small but can grow as needed(memory efficient).
These advantages allow Go program to spawn multiple hundreds or thousand of simultaneous go routines.
Channels
Like maps channels are reference types. When passing a channel to a function, we are passing the pointer to the channel. Like maps and slices, channel zero value is nil.
Channels are not buffered by default. Every write to an open unbuffered channel causes the write channel to stop until another go routines read from that channel. Thus, we can't write or read to an unbuffered channel without aleast two concurrently running goroutines.
Buffer channels. The buffered channel allow limited number of writes without blocking. The subsequent write will be paused if the buffer is full and haven't read from channel. The simple example is that write will be blocked if the buffer is full and read will be blocked if we try to read empty buffered channel.
We can use the comma ok idiom to check whether the channel is closed or not.
The responsibility for closing a channel lies with the goroutine that writes to the
channel. Be aware that closing a channel is required only if a goroutine is waiting for
the channel to close (such as one using a for-range loop to read from the channel).
Since a channel is just another variable, Go’s runtime can detect channels that are no
longer referenced and garbage collect them.
Select
A control structure for concurrency in Go.
Starvation is something that you can't favor one operation over another and some process won't be never processed.
How it works:
It picks randomly of any of it cases that can go forward. The order is unimportant.
Other advantage is it can be used to solve deadlocks.
For{} loop can also be used with select
That combination often referred as for-select loop.
Using default case for select inside for-select is always a wrong thing to do.
It will be triggered everything through the loop if there is nothing match with the cases.
Always clean up goroutines
Unlike variables go runtime can't detect that a goroutine will never be used again. So, we have to make sure that it will eventually exit.
goroutine leak
If a goroutine doesn't exit, all the memory allocated for variables remain allocated and any memeory on the heap that is rooted on the goroutine's stack variables can't be garbage collected. This is called goroutine leak.
Know when to use buffered channel and unbuffered channel
Buffer channels are useful when you know how many go routines you have launched and want to limit the number of goroutines you will launch or want to limit the amount of work that is queued up.
sync.WaitGroup
Sometimes a goroutine need to wait other goroutines, in that case sync.WaitGroup is useful.
But the sync.WaitGroup shouldn't be the first choice when we are dealing with concurrency.
They are useful for cases like closing channels that the goroutines are writing to after all the go routines are exited.
errgroup.Group
Built on top of the sync.WaitGroup to create a set of goroutines that stop processing when one of them returns an error.