More on this book
Kindle Notes & Highlights
responsible for coordinating access to this memory by guarding acces...
This highlight has been truncated due to consecutive passage length restrictions.
lock.Lock() defer lock.Unlock()
You’ll notice that we always call Unlock within a defer statement. This is a very common idiom when utilizing a Mutex to ensure the call always happens, even when panicing. Failing to do so will probably cause your program to deadlock.
The sync.RWMutex is conceptually the same thing as a Mutex: it guards access to memory; however, RWMutex gives you a little bit more control over the memory. You can request a lock for reading, in which case you will be granted access unless the lock is being held for writing. This means that an arbitrary number of readers can hold a reader lock so long as nothing else is holding a writer lock.
but it’s usually advisable to use RWMutex instead of Mutex when it logically makes sense.
As the name implies, sync.Once is a type that utilizes some sync primitives internally to ensure that only one call to Do ever calls the function passed in — even on different goroutines.
Like a river, a channel serves as a conduit for a stream of information; values may be passed along the channel, and then read out downstream. For this reason I usually end my chan variable names with the word “Stream.”
To declare a unidirectional channel, you’ll simply include the <- operator. To both declare and instantiate a channel that can only read, place the <- operator on the lefthand side,
var dataStream <-chan interface{}
And to declare and create a channel that can only send, you place the <- operator on the righthand side,
var dataStream chan<- interface{}
Keep in mind channels are typed.
it is an error to try and write a value onto a read-only channel, and an error to read a value from a write-only channel.
The select statement is the glue that binds channels together; it’s how we’re able to compose channels together in a program to form larger abstractions.
select statements are one of the most crucial things in a Go program with concurrency. You can find select statements binding together channels locally, within a single function or type, and also globally, at the intersection of two or more components in a system. In addition to joining components, at these critical junctures in your program, select statements can help safely bring channels together with concepts like cancellations, timeouts, waiting, and default values.
Unlike switch blocks, case statements in a select block aren’t tested sequentially, and execution won’t automatically fall through if none of the criteria are met.
Instead, all channel reads and writes are considered simultaneously3 to see if any of them are ready: populated or closed channels in the case of reads, and channels that are not at capacity in the case of writes. If none of the channels are ready, the entire select statement blocks. Then when one the channels is ready, that operation will proceed, and its corresponding statements will execute.
What happens when multiple channels have something to read? What if there are never any channels that become ready? What if we want to do something but no channels are currently ready?
If the done channel isn’t closed, we’ll exit the select statement and continue on to the rest of our for loop’s body.
When we enter the select statement, if the done channel hasn’t been closed, we’ll execute the default clause instead.
The goroutine has a few paths to termination: When it has completed its work. When it cannot continue its work due to an unrecoverable error. When it’s told to stop working.
whether or not a child goroutine should continue executing might be predicated on knowledge of the state of many other goroutines. The parent goroutine (often the main goroutine) with this full contextual knowledge should be able to tell its child goroutines to terminate.
Error Handling