The usage of Golang is explained in detail

  • 2020-06-12 09:30:34
  • OfStack

context before Golang version 1.7, is in the package golang org x/net/context, but later found its are needed in many places, all began in 1.7 was added to the Golang standard library. The Context package is designed to simplify operations between multiple goroutines that handle a single request, with respect to data in the request domain, cancellation signals, cut off times, and so on, so this article looks at its usage and implementation.

Source code analysis

First, let's take a look at the core data structures in Context 1:

Context interface


type Context interface {
  Deadline() (deadline time.Time, ok bool)
  Done() <-chan struct{}
  Err() error
  Value(key interface{}) interface{}
}

Deadline returns 1 time.Time, which is the end time for the current Context, indicating whether there is deadline.

The Done method returns 1 channel of close when Context is cancelled or timed out, and channel of close can act as a broadcast notification telling the context related function to stop the current work and return.

The Err method returns why context was canceled.

Value allows Goroutine to share some data, which is of course collate secure. However, be careful with synchronization when using this data, such as the return of an map that is locked for reading and writing.

canceler interface

canceler interface defines context, which provides cancel functions:


type canceler interface {
  cancel(removeFromParent bool, err error)
  Done() <-chan struct{}
}

There are four out of the box implementations:

emptyCtx: empty Context, only achieved Context interface; cancelCtx: Inherited from Context and implemented cancelerinterface timerCtx: Inherited from cancelCtx, can be used to set timeout; valueCtx: Can store 1 key-value pair;

Inheritance Context

The context package provides functions to assist users in creating new Context objects from existing Context objects. These Context objects form a tree: when an Context object is canceled, all Context inherited from it is canceled.

Background is the root of all Context object trees, it cannot be cancelled, it is an instance of emptyCtx:


var (
  background = new(emptyCtx)
)

func Background() Context {
  return background
}

Main methods for generating Context

WithCancel


func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
  c := newCancelCtx(parent)
  propagateCancel(parent, &c)
  return &c, func() { c.cancel(true, Canceled) }
}

Returns 1 cancelCtx example and 1 function that can be called cancelCtx.cancel () directly from the outer layer to cancel Context.

WithDeadline


func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
  if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
    return WithCancel(parent)
  }
  c := &timerCtx{
    cancelCtx: newCancelCtx(parent),
    deadline: deadline,
  }
  propagateCancel(parent, c)
  d := time.Until(deadline)
  if d <= 0 {
    c.cancel(true, DeadlineExceeded) // deadline has already passed
    return c, func() { c.cancel(true, Canceled) }
  }
  c.mu.Lock()
  defer c.mu.Unlock()
  if c.err == nil {
    c.timer = time.AfterFunc(d, func() {
      c.cancel(true, DeadlineExceeded)
    })
  }
  return c, func() { c.cancel(true, Canceled) }
}

An timerCtx example is returned, and the specific deadline time is set. When deadline is reached, the descendant goroutine exits.

WithTimeout


func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
  return WithDeadline(parent, time.Now().Add(timeout))
}

WithDeadline1 and WithDeadline1 samples return an timerCtx example, which is actually WithDeadline packages 1 layer, the duration of the time directly passed in, and exits at the end.

WithValue


func WithValue(parent Context, key, val interface{}) Context {
  if key == nil {
    panic("nil key")
  }
  if !reflect.TypeOf(key).Comparable() {
    panic("key is not comparable")
  }
  return &valueCtx{parent, key, val}
}

WithValue corresponds to valueCtx. WithValue sets one map in Context. The Context and the goroutine of its descendants can get the value in map.

example

The most common use of Context is in the web development of Golang. In Server of http package, each request is handled by a corresponding goroutine. Request handlers typically start additional goroutine to access backend services, such as databases and RPC services. The goroutine used to process a request typically requires access to 1 requests-specific data such as end-user authentication information, token related to authentication, and the request cutoff time. When a request is cancelled or timed out, all goroutine used to process the request should exit quickly before the system can release the resources held by these goroutine. Although we can't kill a goroutine from outside, so I have to make its own end, before we use channel + select way, to solve the problem, but some of the scenes to implement more troublesome, for example by a request derived between each goroutine need to meet the constraints of 1 set, in order to achieve more, such as the period of validity, suspend goroutine tree, transfer request functions such as global variables.

Save context


func middleWare(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    ctx := context.WithValue(req.Context(),"key","value")
    next.ServeHTTP(w, req.WithContext(ctx))
  })
}

func handler(w http.ResponseWriter, req *http.Request) {
  value := req.Context().Value("value").(string)
  fmt.Fprintln(w, "value: ", value)
  return
}

func main() {
  http.Handle("/", middleWare(http.HandlerFunc(handler)))
  http.ListenAndServe(":8080", nil)
}

We can save any type of data in the context to be used throughout the request life cycle.

Timeout control


func longRunningCalculation(timeCost int)chan string{
  result:=make(chan string)
  go func (){
  time.Sleep(time.Second*(time.Duration(timeCost)))
    result<-"Done"
  }()
  return result
}

func jobWithTimeoutHandler(w http.ResponseWriter, r * http.Request){
  ctx,cancel := context.WithTimeout(context.Background(), 3*time.Second)
  defer cancel()

  select{
  case <-ctx.Done():
    log.Println(ctx.Err())
    return
  case result:=<-longRunningCalculation(5):
    io.WriteString(w,result)
  }
  return
}


func main() {
  http.Handle("/", jobWithTimeoutHandler)
  http.ListenAndServe(":8080", nil)
}

Here, 1 timerCtx is used to control the execution time of 1 function. If you exceed this time, you will be interrupted, so you can control 1 longer operation, such as io, RPC call, and so on.

In addition, another important example is the use of cancelCtx, which can be used in multiple goroutine, so as to realize the broadcast function of signals. I will not go into details of specific examples here.

conclusion

The context package achieves the control of Goroutine from the upper layer to Goroutine from the next layer by building Context for tree relationships. You can pass 1 variable to share, you can control timeouts, and you can control the exit of multiple Goroutine.

It is said that in Google, the Golang programmer was required to pass Context as the first parameter to every function on the entry request and exit request links. This ensures that Golang projects developed by multiple teams work well together, and it is a simple timeout and cancellation mechanism that ensures that critical section data is passed smoothly across different Golang projects.

Therefore, being good at context is of great benefit to Golang development, especially web development.


Related articles: