Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

There are three ways to manage Concurrency in the Go language


Jun 01, 2021 Article blog


Table of contents


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.

WaitGroup

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)
}

Channel

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)

Context

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)

summary

Choose different ways based on different situations and situations, and make a summary:

  • WaitGroup: You need to break down a single job into subtasks and wait until it's all done before you can move on to the next step, which is best for WaitGroup
  • Channel - Select : 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
  • Context: If you want to control all 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.