SoFunction
Updated on 2025-03-05

Golang implements application scenarios for controlling concurrency through context

There are many goroutine scenarios in golang. The two most commonly used methods are WaitGroup and Context. Today we will learn about the application scenarios of Context

Use scenarios

Scenario 1: Multiple goroutine execution timeout notification

The most common thing in concurrent execution businesses is that coroutine execution timeouts. If the timeout is not processed, a zombie process will appear. If there is too much accumulation, there will be a rush, so we should avoid them at the source.

See the following example:

package main

import (
 "context"
 "fmt"
 "time"
)

/**
 The same content can control multiple goroutines to ensure that the thread is controllable, instead of having a chan to notify it to close every time a new goroutine is created.
 With him the code is more concise
 */

func main() {
 ("run demo \n\n\n")
 demo()
}

func demo() {
 ctx, cancel := ((), 9*)
 go watch(ctx, "[Thread 1]")
 go watch(ctx, "[Thread 2]")
 go watch(ctx, "[Thread 3]")

 index := 0
 for {
  index++
  ("%d seconds passed \n", index)
  (1 * )
  if index > 10 {
   break
  }
 }

 ("Notify to stop monitoring")
 // In fact, the timeout has been reached at this time, and the coroutine has exited early cancel()

 // Prevent the main process from exiting early (3 * )
 ("done")
}

func watch(ctx , name string) {
 for {
  select {
  case <-():
   ("%s monitoring exited, stopped...\n", name)
   return
  default:
   ("%s goroutine monitoring... \n", name)
   (2 * )
  }
 }
}

Use () to set a time limit for the text stream, and combine for+select to receive messages. When the execution timeout or manually close it, a message will be sent to <-(), and all using the same context will receive this notification, eliminating the tedious code of notifications one by one

Scenario 2: Similar to the session in the web server

For example, in php (without swoole extension), a request comes in, and all information about this request can be obtained from $_REQUEST $_SERVER. Even if you use a global variable, it will serve this request, which is thread-safe.

But golang is different, because the program itself can generate a web sever, so you cannot use global variables casually, otherwise it is a memory leak warning. However, in the actual business, there needs to be something similar to the session to carry the information of a single request. To give a specific example, how to deal with adding a uniqueID to each request? With this uniqueID, all the requested logs can be brought with it, so that when troubleshooting problems, it is convenient to track what happened to a request.

as follows:

func demo2() {
 pCtx, pCancel := (())
 pCtx = (pCtx, "parentKey", "parentVale")
 go watch(pCtx, "[Parent Process 1]")
 go watch(pCtx, "[Parent Process 2]")

 cCtx, cCancel := (pCtx)
 go watch(cCtx, "[Subprocess 1]")
 go watch(cCtx, "[Subprocess 2]")
 (("parentKey"))
 (("parentKey"))

 (10 * )
 ("Subprocess closes")
 cCancel()
 (5 * )
 ("Parent process closed")
 pCancel()

 (3 * )
 ("done")
}

In the first (()), () is a newly created context. Using the characteristics that context can inherit, you can build a context tree of your own program. Execution of cancel() on context will affect the current context and child context, and will not affect the parent.

At the same time, it will also bring a custom value to the context, so that uniqueID can be easily passed, instead of passing parameters layer by layer, changing func or something

For context, applications that are worth referring to:

  • Gin
  • logrus

Context related func and interface

Inheriting context requires the following four interfaces

type Context interface {
 Deadline() (deadline , ok bool)

 Done() <-chan struct{}

 Err() error

 Value(key interface{}) interface{}
}

When using it, there is no need to implement the interface, because the official package has implemented an emptyCtx based on emptyCtx, and the call method has

var (
 background = new(emptyCtx)
 todo  = new(emptyCtx)
)

// This is the initial ctx, and the subsequent child ctxes are inherited from itfunc Background() Context {
 return background
}

// I don't know what context is going to do, but there must be a ctx to use thisfunc TODO() Context {
 return todo
}

Inherited functions

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline ) (Context, CancelFunc)
func WithTimeout(parent Context, timeout ) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
  • WithCancel returns a ctx with cancel function,
  • WithDeadline automatically executes cancel() when the specified time is reached
  • WithTimeout is the shell of WithDeadline. The difference is how much time this function is executed after cancel
func WithTimeout(parent Context, timeout ) (Context, CancelFunc) {
 return WithDeadline(parent, ().Add(timeout))
}

WithValue: When inheriting the parent class ctx, it also brings a value.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.