GOLANG USES Context to manage the method of associating goroutine

  • 2020-06-23 00:34:37
  • OfStack

It is rare for a business not to come to goroutine because many methods are waiting, for example http.Server.ListenAndServe This is waiting and will not return unless Server or Listener are turned off. Unless it is an API server, there is definitely a need for additional goroutine services to be initiated, and for API servers, at http.Handler How to manage these goroutine, provided in GOLANG1.7 context.Context .

First, let's look at a simple one. If two goroutine are started, one is HTTP, and there is also a signal processing signal to receive exit signal for cleaning:


wg := sync.WaitGroup{}
defer wg.Wait()

wg.Add(1)
go func() {
  defer wg.Done()

  ss := make(os.Signal, 0)
  signal.Notify(ss, syscall.SIGINT, syscall.SIGTERM)
  for s := ss {
    fmt.Println("Got signal", s)
    break
  }
}()

wg.Add(1)
go func() {
  defer wg.Done()

  svr := &http.Server{ Addr:":8080", Handler:nil, }
  fmt.Println(svr.ListenAndServe())
}

Clearly, two goroutine are raised, then WaitGroup is used to wait for them to exit. If they don't interact and interact with each other, that's really easy, but it doesn't work, because goroutine of the signal should tell server to quit when it receives the exit signal. The violent 1 point is called directly svr.Close() But what if some requests still need to be canceled? Better use Context:


wg := sync.WaitGroup{}
defer wg.Wait()

ctx,cancel := context.WithCancel(context.Background())

wg.Add(1)
go func() {
  defer wg.Done()

  ss := make(chan os.Signal, 0)
  signal.Notify(ss, syscall.SIGINT, syscall.SIGTERM)
  select {
  case <- ctx.Done():
    return
  case s := <- ss:
    fmt.Println("Got signal", s)
    cancel() //  Cancel the request, the notification is used ctx All of the goroutine
    return
  }
}()

wg.Add(1)
go func() {
  defer wg.Done()
  defer cancel()

  svr := &http.Server{ Addr:":8080", Handler:nil, }

  go func(){
    select {
    case <- ctx.Done():
      svr.Close()
    }
  }

  fmt.Println(svr.ListenAndServe())
}

This method can continue to be used when new goroutine is opened, such as adding a new goroutine with UDPConn read and written in it:


wg.Add(1)
go func() {
  defer wg.Done()
  defer cancel()

  var conn *net.UDPConn
  if conn,err = net.Dial("udp", "127.0.0.1:1935"); err != nil {
    fmt.Println("Dial UDP server failed, err is", err)
    return
  }

  fmt.Println(UDPRead(ctx, conn))
}()

UDPRead = func(ctx context.Context, conn *net.UDPConn) (err error) {
  wg := sync.WaitGroup{}
  defer wg.Wait()

  ctx, cancel := context.WithCancel(ctx)

  wg.Add(1)
  go func() {
    defer wg.Done()
    defer cancel()

    for {
      b := make([]byte, core.MTUSize)
      size, _, err := conn.ReadFromUDP(b)
      //  To deal with UDP package  b[:size]
    }
  }()

  select {
  case <-ctx.Done():
    conn.Close()
  }
  return
}

If you only use HTTP Server, you can write this:


func run(ctx contex.Context) {
  server := &http.Server{Addr: addr, Handler: nil}
  go func() {
    select {
    case <-ctx.Done():
      server.Close()
    }
  }()

  http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
  })

  fmt.Println(server.ListenAndServe())
}

If you need to provide an API to make the server quit, write this:


func run(ctx contex.Context) {
  server := &http.Server{Addr: addr, Handler: nil}

  ctx, cancel := context.WithCancel(ctx)
  http.HandleFunc("/quit", func(w http.ResponseWriter, r *http.Request) {
    cancel() //  Use local ctx and cancel
  })

  go func() {
    select {
    case <-ctx.Done():
      server.Close()
    }
  }()

  fmt.Println(server.ListenAndServe())
}

Using local ctx and cancel, you can avoid cancel passing in ctx, only affecting the current ctx.


Related articles: