Go Concurrency
Composition of multiple independent operations.
Goroutine : An independently executed function, launched by the goroutine.
Goroutines are cheap threads, but not reaparallel.
Go channel : A way for goroutines to communicate.
Go channels block till data is sent.
Buffered Go channels block only when the buffer is full.
- So let's take a refresher into how concurrency works in Go.
- Concurrency is a way to execute independent operations concurrently.
- A go routine is go's answer to threads.
- It provides a way to write code concurrently.
- Technically though they are not really threads.
- However they serve a similar purpose by being multiplexed amongst the OS threads.
- So you can think of them as cheap threats.
- A go channel is a channel between two go routines where data can be passed.
- Whenever there is data getting sent on a channel, both receiver go routine and the sender go routine wait till the data gets past a buffer channel is a special type of channel that can contain multiple values and only blocks when its buffer is full.
- We covered Go concurrency in section 3 before this section.
- Now let's talk about the popular concurrency design pattern in Go.
Channel Generators
Functions or methods that return channels
Removes the need to define the channel outside
Enables more efficient and fluid code
- A channel generator is simply a method or a function that returns a channel by which it communicates with the outside world
- Channel generators are popular because it removes ???the clutter of having to define here channels everywhere in your code.
- Whenever we work with a Go routine that uses the channel.
- This improves the efficiency as well as the readability of your code.
- So let's see some code to support this definition.
Final ticker example
Before
func main() {
ticker := time.NewTicker(1 * time.Second)
done := make(chan bool)
go tickCounter(ticker, done)
time.Sleep(5 * time.Second)
ticker.Stop()
done <- true
time.Sleep(10 * time.Second)
fmt.Println("Exting...")
}
func tickCounter(ticker *time.Ticker, done chan bool) {
i := 0
Loop:
for {
select {
case t := <-ticker.C:
i++
fmt.Println("Count ", i, " at ", t)
case <-done:
fmt.Println("done signal")
break Loop
}
}
fmt.Println("Exiting the tick counter")
}
After
func main() {
ticker := time.NewTicker(1 * time.Second)
done := tickCounter(ticker)
time.Sleep(5 * time.Second)
ticker.Stop()
done <- true
time.Sleep(10 * time.Second)
fmt.Println("Exting...")
}
func tickCounter(ticker *time.Ticker) chan bool{
i := 0
done := make(chan bool)
go func(){
Loop:
for {
select {
case t := <-ticker.C:
i++
fmt.Println("Count ", i, " at ", t)
case <-done:
fmt.Println("done signal")
break Loop
}
}
fmt.Println("Exiting the tick counter")
}()
return done
}
Result
Count 1 at 2009-11-10 23:00:01 +0000 UTC m=+1.000000001
Count 2 at 2009-11-10 23:00:02 +0000 UTC m=+2.000000001
Count 3 at 2009-11-10 23:00:03 +0000 UTC m=+3.000000001
Count 4 at 2009-11-10 23:00:04 +0000 UTC m=+4.000000001
Count 5 at 2009-11-10 23:00:05 +0000 UTC m=+5.000000001
done signal
Exiting the tick counter
https://go.dev/play/p/UCKiBlE1eG5
- Remember our final ticker example.
- We had to create a done channel which was used inside the tickCounter Go rountine.
- They presented a signal that the Go routine used to exit.
- If we create a channel generator, we don't really need to add the channel as an argument.
- And we also do not need to define it outside for Go routine.
- So let's see how we can do that.
- So if we encapsulate the loop project of tickCounter function in another Go routine.
- Then if we define done.
- Inside our code here.
- And then make a tickCounter ????? return a value type channel bool.
- We can then return done at the end of our function here.
- So look at the tickCounter now, We'll see that the structure of a function has changed.
- So now we do some initialization at the beginning.
- So initialize i and we also initialize done channel.
- Then we start a new go routine which is where the main for loop will happen.
- And this is where we will listen to the tick channel as well as the done signal in a for loop.
- Outside of the for internal go routine, we can return the channel done.
- So now in our external code instead of the defining done at the beginning
- We can stop doing that.
- And then instead of passing done as an argument, we can also just pass the thicker as an argument to full value syntax???.
- Now we don't also need to worry about running tickCounter on a separate Goroutine.
- That is because now the main for loop in the tickCounter lives inside of a routine.
- We can even more than initialization of a counter and send a Go routine as well.
- And now all what happens outside of a go routine is declaring the done channel and returning the done channel.
- Now All what we need to do in the main function, when we call tickCounter is retrieve tickCounters return value which is a done bool channel.
- We also need to fix the code here because this is a bool channel so we can now format the code and then run and it will run as before.
- Now our code is much cleaner and does the same task.
Summary
- We discussed our popular design pattern called Channel generators.
- We learned how to implemented and why it's useful.
댓글 영역