Golang Concurrency

1. log.Fatal 盡量用在main 或 init

log.Fatal 調用了 os.Exit(), 會無條件終止程式; defer不會被調用到

2. Keep yourself busy or do the work yourself.

啟 goroutine 的人一定要在調用者

3. Never start a goroutine without knowning when it will stop.

你必需知道goroutine 何時被停止

goroutine leak

Bad: goroutine leak 因為 ch 是在 leak function 內. 當 leak執行完後, 裡面的 go func() 還在等 ch 的值, 此 goroutine 被 block.

// leak is a buggy function, 
// It launches a goroutine that block receiving from a channel. 
// Nothing will ever be sent on that channel and the channel is never closed so that goroutine will be blocked forever.
func leak() {
  ch := make(chan int)

  go func() {
    val := <-ch
    fmt.Prinln("We received a value:", val)
  }()
}

超時控制

Bad:

// search simulates a function that finds a record based on a search term.
// It takes 200 ms to perform this work.
func search(term string) (string, error) {
  time.Sleep(200 * time.Millisecond)
  return "some value", nil
}

// process is the work for the program.
// It finds a record then prints it.
func process(term string) error {
  record, err := search(term)
  if err != nil {
    return err
  }

  fmt.Println("Received: ", record)
  return nil
}

Good:

type result struct {
  record string
  err error
}

// process is the work for the program.
// It finds a record then prints it.
func process(term string) error {
  // Create a context that will be canceled in 100 ms
  ctx, cancel := context.WithTimeout(context.Backgroud(), 100*time.Millisecond)
  defer cancel()

  // Make a channel for the goroutine to report its result.
  ch := make(chan result)

  // Launch a goroutine to find the record. Create a result 
  // from the returned values to send through the channel.
  go func() {
    record, err := search(term)
    ch <- result{record, err}
  }

  // Block waiting to either receive from the goroutine's
  // channel or for the context to be canceled.
  select {
    case <-ctx.Done():
      return errors.New("search canceled")
    case result := <-ch
      if result.err != nil{
        return result.err
      }
      fmt.Println("Received: ", result.record)
      return nil
  }
}

超時控制 2

Bad 1: 無法保證創建的 goroutine 生命週期管理, 會導致在服務關閉時, 有些事件丟失

// Tracker knows how to track events for the application
type Tracker struct{}

// Event records an event to a database or stream.
func (t *Tracker) Event(data string) {
  time.Sleep(time.Millisecond) // Simulate network write latency.
  log.Println(data)
}

type App struct {
  track Tracker
}

func (a *App) Handle(w http.ResponseWriter, r * http.Request) {
  // Do some actual work.

  // Respond to the client
  w.WriteHeader(http.StatusCreated)

  // Fire and Hope.
  // BUG: We are not managing this goroutine
  go a.track.Event("this event")
}

Bad 2: 使用 sync.WaitGroup 來追蹤每一個創建的 goroutine 大量創建 goroutine 代價高

// Tracker knows how to track events for the application
type Tracker struct {
  wg sync.WaitGroup
}

// Event stars tracking an event. It runs asynchronously to
// not block the caller. Be sure to call the Shutdown function
// before the program exits so all tracked events finish.
func (t *Tracker) Event(data string) {
  // Increment counter so Shutdown knows to wait for this event.
  t.wg.Add(1)

  // Track event in a goroutine so caller is not blocked.
  go func(){
    // Decrement counter to tell Shutdown this goroutine finished.
    defer t.wg.Done()

    time.Sleep(time.Millisecond) // Simulate network write latency.
    log.Println(data)
  }()
}

func (t *Tracker) Shutdown() {
  t.wg.Wait()
}

func main() {
  // Start a server.
  // Details not shown...
  var a App

  // Shut the server down.
  // Details not shown...

  // Wait for all event goroutines to finish
  a.track.Shutdown()
}

Good 1: // M Sender one Receiver : 由 Receiver 發起一個close到額外的一個 channel // Sender: tr.Event() ; Receiver tr.Run()

package main

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

type Tracker struct {
    ch   chan string
    stop chan struct{}
}

// Tracker knows how to track events for the application.
func (t *Tracker) Event(ctx context.Context, data string) error {
    select {
    case t.ch <- data:
        return nil
    case <-ctx.Done():
        return ctx.Err()
    }
}

func (t *Tracker) Run() {
    for data := range t.ch {
        time.Sleep(1 * time.Second)
        fmt.Println(data)
    }
    t.stop <- struct{}{}
}

func (t Tracker) Shutdown(ctx context.Context) {
    close(t.ch)
    select {
    case <-t.stop:
    case <-ctx.Done():
    }
}

func NewTracker() *Tracker {
    return &Tracker{
        ch: make(chan string, 10),
    }
}

func main() {
    tr := NewTracker()
    go tr.Run()
    _ = tr.Event(context.Background(), "test")
    _ = tr.Event(context.Background(), "test")
    _ = tr.Event(context.Background(), "test")
    ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
    defer cancel()
    tr.Shutdown(ctx)
}

// M Sender one Receiver : 由 Receiver 發起一個close到額外的一個 channel
// Sender: tr.Event() ; Receiver tr.Run()

Good 2:

package main

import (
    "fmt"
    "sync"
    "time"
)

type Tracker struct {
    ch   chan string
    stop chan struct{}
}

// Tracker knows how to track events for the application.
func (t *Tracker) Event(data string) error {
    select {
    case <-t.stop:
        fmt.Println("Event: t.stop 1")
        return nil
    default:
    }

    select {
    case t.ch <- data:
        // fmt.Println("send : ", data)
        return nil
    case <-t.stop:
        fmt.Println("Event: t.stop 2")
        return nil
    }
}

// one receiver
func (t *Tracker) Run() {
    for {
        select {
        case data := <-t.ch:
            time.Sleep(1000 * time.Millisecond)
            fmt.Println(data)
            if data == "test:15" {
                close(t.stop)
            }
        case <-t.stop:
            return
        }
    }
}

func NewTracker() *Tracker {
    return &Tracker{
        ch:   make(chan string, 10),
        stop: make(chan struct{}),
    }
}

func main() {
    tr := NewTracker()

    const NumSenders = 50
    const Timeout = 3

    // N senders
    for i := 0; i < NumSenders; i++ {
        go func(idx int) {
            msg := fmt.Sprintf("%s:%d", "test", idx)
            _ = tr.Event(msg)
        }(i)
    }

    go func() {
        // select {
        // case <-time.After(Timeout * time.Second):
        //     close(tr.stop)
        // }
        time.Sleep(Timeout * time.Second)
        close(tr.stop)
    }()

    // One receiver
    wg := sync.WaitGroup{}
    wg.Add(1)
    go func() {
        defer wg.Done()
        tr.Run()
    }()
    wg.Wait()

}

// M Sender one Receiver : 由 Receiver 發起一個close到額外的一個 channel
// Sender: tr.Event() ; Receiver tr.Run()

serve

Good:

func main() {
    done := make(chan error, 2)
    stop := make(chan struct{})

    go func() {
        done <- serveDebug(stop)
    }()
    go func() {
        done <- serveApp(stop)
    }()

    var stopped bool
    for i := 0 ; i < cap(done); i++ {
        if err := <-done; err != nil {
            fmt.Println("error : %v", err)
        }
        if !stopped {
            stopped = true
            close(stop)
        }
    }
}
func serve(addr string, handler http.Handler, stop <-chan strcut{}) error {
    s := http.Server{
        Addr: addr,
        Handler: handler,
    }

    go func() {
        <-stop // wait for stop signal
        s.Shutdown(context.Backgroud())
    }()

    return s.ListenAndServe()
}

https://github.com/da440dil/go-workgroup

4. Leave concurrency to the caller

Bad 1:

func ListDirectory(dir string)([]string, error)

根據目錄的大小, 這可能需要很長時間, 並且可能分配大量的內存來構建目錄列表名稱的slice

Bad 2:

func ListDirectory(dir string) chan string

當中途遇到了錯誤, 調用方無法區分是空目錄或者是讀取錯誤, 這兩種錯誤都會導致 channel關閉. 調用者必須繼續從通道讀取, 直到它關閉. 對於中大型目錄, 它可能在內存使用方面更為高效, 但這種方法並不比原始的slice 方法快.

Good: 參考 filepath: https://pkg.go.dev/path/filepath#WalkDir 如果函數啟動 goroutine , 則必須向調用方提供顯示停止該 goroutine 的方法. 通常將異步執行函數的決定權交給該函數的調用者通常更容易

func WalkDir(root string, fn fs.WalkDirFunc) error {
  info, err := os.Lstat(root)
  if err != nil {
    err = fn(root, nil, err)
  } else {
    err = walkDir(root, &statDirEntry{info}, fn)
  }
  if err == SkipDir {
    return nil
  }
  return err
}
© Kimi Tsai all right reserved.            Updated : 2023-07-12 09:04:53

results matching ""

    No results matching ""

    results matching ""

      No results matching ""