Go language: context of concurrency control artifact

Time:2021-8-14

How to exit the process

After a collaboration is started, it usually exits automatically after the code is executed, but what if it needs to be terminated in advance?
One way is to define a global variable, and determine whether to exit by checking the change of this variable. This method requires locking to ensure concurrency security. Speaking of this, do you think of any solution?
select + channelTo achieve:

package main
import (
    "fmt"
    "sync"
    "time"
)
func main() {
    var wg sync.WaitGroup
    stopWk := make(chan bool)
    wg.Add(1)
    go func() {
        defer wg.Done()
        worker(stopWk)
    }()
    Time. Sleep (3 * time. Second) // working for 3 seconds
    Stopwk < - true // issue stop command after 3 seconds
    wg.Wait()
}

func worker(stopWk chan bool){
    for {
        select {
        case <- stopWk:
            FMT. Println ("off duty ~ ~")
            return
        default:
            FMT. Println ("fishing carefully, don't disturb...)
        }
        time.Sleep(1*time.Second)
    }
}

Operation results:

Seriously fishing, please don't disturb
Seriously fishing, please don't disturb
Seriously fishing, please don't disturb
Off duty~~~

It can be seen that “seriously fishing, please don’t disturb…” is printed once a second. After 3 seconds, a stop instruction is issued, and the program enters “work ~ ~ ~”.

Context initial experience

Above, we used select + channel to terminate the collaboration process, but what if we want to cancel multiple collaboration processes at the same time? What if you need to cancel regularly?
At this point, context needs to appear. It can track each collaboration. Let’s rewrite the above example:

package main
import (
    "context"
    "fmt"
    "sync"
    "time"
)
func main() {
    var wg sync.WaitGroup
    ctx, stop := context.WithCancel(context.Background())
    wg.Add(1)
    go func() {
        defer wg.Done()
        worker(ctx)
    }()
    Time. Sleep (3 * time. Second) // working for 3 seconds
    Stop() // issue the stop command after 3 seconds
    wg.Wait()
}

func worker(ctx context.Context){
    for {
        select {
        case <- ctx.Done():
            FMT. Println ("off duty ~ ~")
            return
        default:
            FMT. Println ("fishing carefully, don't disturb...)
        }
        time.Sleep(1*time.Second)
    }
}

Operation results:

Seriously fishing, please don't disturb
Seriously fishing, please don't disturb
Seriously fishing, please don't disturb
Off duty~~~

Context introduction

Context is concurrent and safe. It is an interface that can send cancellation signals and transfer values manually, regularly and overtime. It is mainly used to control the cooperation and cancellation operations among multiple processes.

The context interface has four methods:

type Context interface {
   Deadline() (deadline time.Time, ok bool)
   Done() <-chan struct{}
   Err() error
   Value(key interface{}) interface{}
}
  • DeadlineMethod: you can get the set deadline. The return value deadline is the deadline. At this time, context will automatically initiate a cancellation request. The return value OK indicates whether the deadline is set.
  • DoneMethod: return a read-only channel of type struct {}. If the Chan can be read, it indicates that a cancellation signal has been sent. You can clean up, and then exit the process to release resources.
  • ErrMethod: returns the reason why the context was canceled.
  • ValueMethod: obtain the value bound on the context, which is a key value pair, and obtain the corresponding value through the key.

The most commonly used method is the done method. When the context is cancelled, the read-only channel will be closed, which is equivalent to sending a cancellation signal.

Context tree

We don’t need to implement the context interface ourselves. Go language provides functions to generate different contexts. Through these functions, we can generate a context tree, so that the context can be associated. The parent context will send a cancel signal, and the child context will also send a cancel signal, so that we can control the exit of different levels of processes.

Generate root node

  1. emptyCtxIs a variable of type int, but implements the interface of context.emptyCtxThere is no timeout, can’t be cancelled, and can’t store any additional information, soemptyCtxUsed as the root node of the context tree.
  2. But we usually don’t use it directlyemptyCtxInstead, useemptyCtxTwo instantiated variables (background and todo) are called respectivelyBackgroundandTODOMethod, but the two contexts are the same in implementation.

Differences between background and todo methods:
BackgroundandTODOOnly for different scenarios:BackgroundIt is usually used in main functions, initialization and testing as a top-levelcontextThat is, generally we createcontextAll based onBackground; andTODOI’m not sure what to usecontextIt will only be used when.

Spanning tree function

  1. Can passcontext。Background()Get a root node context.
  2. After you have the root node, you can use the following four functions to generate the context tree:
  3. WithCancel(parent Context): generate a cancelable context.
  4. WithDeadline(parent Context, d time.Time): generate a context that can be cancelled regularly. Parameter D is the specific time of scheduled cancellation.
  5. WithTimeout(parent Context, timeout time.Duration): generate a context that can be cancelled after timeout. The parameter timeout is used to set how long to cancel after timeout
  6. WithValue(parent Context, key, val interface{}): generate a context that can carry key value pairs.

Context cancel multiple coroutines

If a context has child contexts, all child contexts under it will be cancelled when the context is cancelled.

Go language: context of concurrency control artifact

Context value transfer

Context can not only send a cancellation signal, but also pass values. It can provide the stored values for other processes.

Example:

package main
import (
    "context"
    "fmt"
    "sync"
    "time"
)
func main() {
    var wg sync.WaitGroup
    ctx, stop := context.WithCancel(context.Background())
    valCtx := context.WithValue(ctx, "position","gopher")
    wg.Add(2)
    go func() {
        defer wg.Done()
        Worker (valctx, "worker 1")
    }()
    go func() {
        defer wg.Done()
        Worker (valctx, "worker 2")
    }()
    Time. Sleep (3 * time. Second) // working for 3 seconds
    Stop() // issue the stop command after 3 seconds
    wg.Wait()
}

func worker(valCtx context.Context, name string){
    for {
        select {
        case <- valCtx.Done():
            FMT. Println ("off duty ~ ~")
            return
        default:
            position := valCtx.Value("position")
            FMT. Println (name, position, "fishing carefully, don't disturb...)
        }
        time.Sleep(1*time.Second)
    }
}

Operation results:

Worker 2 gopher is fishing seriously. Please don't disturb
Worker 1 gopher is fishing seriously. Please don't disturb
Worker 1 gopher is fishing seriously. Please don't disturb
Worker 2 gopher is fishing seriously. Please don't disturb
Worker 2 gopher is fishing seriously. Please don't disturb
Worker 1 gopher is fishing seriously. Please don't disturb
Off duty~~~
Off duty~~~

Context usage principle

  • Context should not be placed in the structure, but should be passed as a parameter
  • When context is used as a function parameter, it should be placed first as the first parameter
  • Use context. The background function generates the context of the root node
  • Context should pass the necessary values, not everything
  • Context is multi process safe and can be used in multiple processes