← Concurrency In Go

Chapter-3(Go Concurrency Building blocks)

Golangconcurrency

What are Go routines?

They are not OS thread nor green threads. Similar to green thread but with better optimized version.
Managed by runtime, higher level of abstraction known as coroutines.
Coroutines are simply concurrent routines and they cannot be interrupted.

GO use M:N scheduler.
Map the goroutines M to the N OS threads.

When we have more goroutines than the OS thread, the scheduler handle the distribution of goroutines across the available threads. Ensures that when these goroutines become blocked, other goroutines can be run.

Go follows a model of concurrency called the fork-join model.1 The word fork refers to the fact that at any point in the program, it can split off a child branch of execution to be run concurrently with its parent. The word join refers to the fact that at some point in the future, these concurrent branches of execution will join back together. Where the child rejoins the parent is called a join point.

Sync RWMutex

Gives more control over the memory. It has Read Lock and Write Lock.
Many goroutines can use Read Lock but only one goroutine can use write lock.
The reason is Read Lock and reading does nothing changes in the memory. But the Write Lock does. So, to prevent a go routine writing to the memory that other go routine is reading and multiple go routines is not supposed to write to a single memory location and thus, only one write lock can be used at a time.

Wait() from Sync.Cond doesn't block the Entire OS thread

Its like triggering by the state change. Such as Signal()
When the go routine is set to Wait(), that go routine is removed from the run queue until we get a signal. When we get the Signal() that go routine is put back to the run queue.

Wait() from WaitGroup blocks

Trigger by the completion.
We add a certain amount of go routines to the Wait Group with Add.
Then when those added go routines are finished, proceed to next.

Pool

The object pool design pattern is best used either when you have con‐ current processes that require objects, but dispose of them very rapidly after instan‐ tiation, or when construction of these objects could negatively impact memory.

when working with a Pool, just remember the following points:
• When instantiating sync.Pool, give it a New member variable that is thread-safe when called.
• When you receive an instance from Get, make no assumptions regarding the state of the object you receive back.
• Make sure to call Put when you’re finished with the object you pulled out of the pool. Otherwise, the Pool is useless. Usually this is done with defer.
• Objects in the pool must be roughly uniform in makeup.

Channel

Types

Unidirectional channel declarations are the tool that will allow us to distinguish between goroutines that own channels and those that only utilize them

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 left‐ hand side, like so:

var dataStream <-chan interface{} 
dataStream := make(<-chan interface{}) 

And to declare and create a channel that can only send, you place the <- operator on the righthand side, like so:

var dataStream chan<- interface{} 
dataStream := make(chan<- interface{})

You don’t often see unidirectional channels instantiated, but you’ll often see them used as function parameters and return types, which is very useful, as we’ll see. This is possible because Go will implicitly convert bidirectional channels to unidirectional channels when needed. Here’s an example:

var receiveChan <-chan interface{} 
var sendChan chan<- interface{} 
dataStream := make(chan interface{}) 
// Valid statements: 
receiveChan = dataStream 
sendChan = dataStream

Channel Ownership

Assigning Channel Ownership is recommended because by using channel ownerships that make the program much easier to reason about. Like we can easily differ which function is the main one creating and inputting to the channel and which function is just utilizing it.

Let’s begin with channel owners. The goroutine that owns a channel should:

  1. Instantiate the channel.
  2. Perform writes, or pass ownership to another goroutine.
  3. Close the channel.
  4. Ecapsulate the previous three things in this list and expose them via a reader channel.

Example

chanOwner := func() <-chan int { resultStream := make(chan int, 5) 
go func() { 
	defer close(resultStream) 
	for i := 0; i <= 5; i++ { 
		resultStream <- i 
		} 
}() 
return resultStream } 
resultStream := chanOwner() 
for result := range resultStream { 
	fmt.Printf("Received: %d\n", result) 
	} 
fmt.Println("Done receiving!")

Select

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.

If the multiple channels are populate at the same time, a case will be selected in random.
For case like channels are never populated, in that case adding timeout to the case is a solution.
Another way is to use default case. If all the channels are blocked, the default case will run.