Jun 01, 2021 Article blog
Believe that everyone step into the world of
Go
language, must be attracted by the powerful
Concurrency
Go
language with the simplest keyword
go
can throw tasks into the background processing, but how developers can effectively control concurrency, this is the skills to get started
Go
language must learn, this chapter will introduce several ways to bring people to know concurrency, and these three ways correspond to three different nouns:
WaitGroup
Channel
and
Context
Here's a simple example to take you to.
Let's see what scenarios need to be used with
WaitGroup
assuming you have two machines that need to upload the latest code at the same time, and two machines that are uploaded separately before you can perform the final restart step.
It's like splitting a job into several copies at the same time, reducing time, but finally waiting until it's all done to take the next step, which requires
WaitGroup
to do it.
(Recommended course: Go tutorial)
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
i := 0
wg.Add(3) //task count wait to do
go func() {
defer wg.Done() // finish task1
fmt.Println("goroutine 1 done")
i++
}()
go func() {
defer wg.Done() // finish task2
fmt.Println("goroutine 2 done")
i++
}()
go func() {
defer wg.Done() // finish task3
fmt.Println("goroutine 3 done")
i++
}()
wg.Wait() // wait for tasks to be done
fmt.Println("all goroutine done")
fmt.Println(i)
}
Another practical case is that we need to proactively notify a
Goroutine
to stop the action. I
n other words, when the app starts, it runs some monitors in the background, and when the entire app needs to stop, you need to send a
Notification
to the background monitor and stop it first, which requires
Channel
to notify you.
Take a look at the following example:
package main
import (
"fmt"
"time"
)
func main() {
exit := make(chan bool)
go func() {
for {
select {
case <-exit:
fmt.Println("Exit")
return
case <-time.After(2 * time.Second):
fmt.Println("Monitoring")
}
}
}()
time.Sleep(5 * time.Second)
fmt.Println("Notify Exit")
exit <- true //keep main goroutine alive
time.Sleep(5 * time.Second)
}
As you can see from the above example, a
Gogourtine
and
Channel
are used to control it. I
magine that when there are countless
Goroutine
in the background, we need to use multiple
Channel
to control them, and maybe
Goroutine
will be generated within
Goroutine
and developers will find that they can no longer control multiple
Goroutine
using
Channel
alone.
This is when the solution is to pass
Context
(Recommended course: Go Web programming)
As you can imagine, today there is a background task A, A task produces b task, B task produces C task, that is, can continue to produce in this pattern, assuming that we need to stop the A task in the middle, and A must tell B and C to stop together, this time through
context
method is the fastest.
package main
import (
"context"
"fmt"
"time"
)
func foo(ctx context.Context, name string) {
go bar(ctx, name) // A calls B
for {
select {
case <-ctx.Done():
fmt.Println(name, "A Exit")
return
case <-time.After(1 * time.Second):
fmt.Println(name, "A do something")
}
}
}
func bar(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name, "B Exit")
return
case <-time.After(2 * time.Second):
fmt.Println(name, "B do something")
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go foo(ctx, "FooBar")
fmt.Println("client release connection, need to notify A, B exit")
time.Sleep(5 * time.Second)
cancel() //mock client exit, and pass the signal, ctx.Done() gets the signal time.Sleep(3 * time.Second)
time.Sleep(3 * time.Second)
}
package main
import (
"context"
"fmt"
"time"
)
func foo(ctx context.Context, name string) {
go bar(ctx, name) // A calls B
for {
select {
case <-ctx.Done():
fmt.Println(name, "A Exit")
return
case <-time.After(1 * time.Second):
fmt.Println(name, "A do something")
}
}
}
func bar(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name, "B Exit")
return
case <-time.After(2 * time.Second):
fmt.Println(name, "B do something")
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go foo(ctx, "FooBar")
fmt.Println("client release connection, need to notify A, B exit")
time.Sleep(5 * time.Second)
cancel() //mock client exit, and pass the signal, ctx.Done() gets the signal time.Sleep(3 * time.Second)
time.Sleep(3 * time.Second)
}
You can think of
context
as a
controller
you can control the number of uncertain
Goroutine
at any time, from top to bottom, as long as the
context.WithCancel
the entire background service can be stopped at any point in time through
cancel()
The actual case is used to notify all
goroutine
stops when the app needs to be restarted, and the app is not restarted until it stops normally.
(Recommended micro-class: Go micro-class)
Choose different ways based on different situations and situations, and make a summary:
WaitGroup
Channel
can only be used in a more pure
Goroutine
situation, and if you want to manage multiple
Goroutine
it is recommended that you go
context
Goroutine
at once, I believe that
context
would be the most appropriate, of course,
context
is not only this feature, you can refer to the "10 minutes to understand the Go language context package usage scenarios and introductions"
Here's a look at three ways to manage
Concurrency
in the Go language.