头图

How to exit the coroutine

After a coroutine is started, it usually exits automatically after the code is executed, but what if it needs to be terminated early?
One way is to define a global variable, and the coroutine determines whether to exit by checking the change of this variable. This method requires locking to ensure concurrency safety. So far, are there any solutions you can think of?
select + channel to 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) //工作3秒
    stopWk <- true //3秒后发出停止指令
    wg.Wait()
}

func worker(stopWk chan bool){
    for {
        select {
        case <- stopWk:
            fmt.Println("下班咯~~~")
            return
        default:
            fmt.Println("认真摸鱼中,请勿打扰...")
        }
        time.Sleep(1*time.Second)
    }
}

operation result:

认真摸鱼中,请勿打扰...
认真摸鱼中,请勿打扰...
认真摸鱼中,请勿打扰...
下班咯~~~

you can see that "Fishing fish seriously, please do not disturb..." is printed once every second, and a stop command is issued after 3 seconds, and the program enters "off work~~~".

Context first experience

Above we used select+channel to achieve the termination of the coroutine, but what if we want to cancel multiple coroutines at the same time? What if you need to cancel regularly?
At this point, Context needs to come on stage, it can track each coroutine, we 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) //工作3秒
    stop() //3秒后发出停止指令
    wg.Wait()
}

func worker(ctx context.Context){
    for {
        select {
        case <- ctx.Done():
            fmt.Println("下班咯~~~")
            return
        default:
            fmt.Println("认真摸鱼中,请勿打扰...")
        }
        time.Sleep(1*time.Second)
    }
}

operation result:

认真摸鱼中,请勿打扰...
认真摸鱼中,请勿打扰...
认真摸鱼中,请勿打扰...
下班咯~~~

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 between multiple coroutines.

The Context interface has four methods:

type Context interface {
   Deadline() (deadline time.Time, ok bool)
   Done() <-chan struct{}
   Err() error
   Value(key interface{}) interface{}
}
  • Deadline Method: 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.
  • Done method: return a read-only channel, the type is struct{}. If the chan can be read, it means that the cancel signal has been sent, and the cleanup operation can be done, and then exit the coroutine to release the resources.
  • Err Method: Return the reason why the Context was cancelled.
  • Value Method: Get the value bound on the Context, which is a key-value pair, and get 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 issuing a cancellation signal.

Context tree

We don’t need to implement the Context interface ourselves. The Go language provides functions to generate different Contexts. Through these functions, a Context tree can be generated so that the Contexts can be associated. The parent Context issues a cancellation signal, and the child Context also Issued, so that you can control the exit of different levels of coroutines.

Generate root node

  1. emptyCtx is an int type variable, but it implements the context interface. emptyCtx has no timeout period, cannot be cancelled, nor can it store any additional information, so emptyCtx used as the root node of the context tree.
  2. But we generally do not use emptyCtx directly, but use two variables (background, todo) instantiated emptyCtx Background and TODO methods, respectively, but the two contexts are the same in implementation.
Background and TODO method difference:
Background and TODO are only used in different scenarios: Background is usually used in main functions, initialization and testing, as a top-level context , which means that generally we create context based on Background ; and TODO is not sure what to use context used when 060fcbea09362e.

Spanning tree function

  1. Can pass context. Background() gets a root node Context.
  2. After having the root node, 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, and the parameter d is the specific time of the scheduled cancellation.
  5. WithTimeout(parent Context, timeout time.Duration) : Generate a Context that can be cancelled over time, the parameter timeout is used to set how long to cancel
  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 Context, when the Context is cancelled, all the child Contexts under it will be cancelled.

image.png

Context pass by value

Context can not only issue cancellation signals, but also pass values, and provide the stored value for other coroutines to use.

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, "打工人1")
    }()
    go func() {
        defer wg.Done()
        worker(valCtx, "打工人2")
    }()
    time.Sleep(3*time.Second) //工作3秒
    stop() //3秒后发出停止指令
    wg.Wait()
}

func worker(valCtx context.Context, name string){
    for {
        select {
        case <- valCtx.Done():
            fmt.Println("下班咯~~~")
            return
        default:
            position := valCtx.Value("position")
            fmt.Println(name,position, "认真摸鱼中,请勿打扰...")
        }
        time.Sleep(1*time.Second)
    }
}

operation result:

打工人2 gopher 认真摸鱼中,请勿打扰...
打工人1 gopher 认真摸鱼中,请勿打扰...
打工人1 gopher 认真摸鱼中,请勿打扰...
打工人2 gopher 认真摸鱼中,请勿打扰...
打工人2 gopher 认真摸鱼中,请勿打扰...
打工人1 gopher 认真摸鱼中,请勿打扰...
下班咯~~~
下班咯~~~

Principles of using Context

  • Context should not be placed in the structure, it needs to 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 must pass the necessary values, don’t pass everything
  • Context is safe for multiple coroutines and can be used in multiple coroutines

微客鸟窝
37 声望3 粉丝