Open4

go context

engineer rebornengineer reborn

テスト

参考

https://zenn.dev/hsaki/books/golang-context/viewer/definition

context

複数のgoルーチンを管理する時に、利用する
=> contextはチャネルを内部で実装されている
=> チャネルも複数のgoルーチンをコントロールする。chan struct{}

利用イメージ

  • 既存ライブラリなどのプログラマが意識しない処理への情報の電波

contextでよく使うやつ

  • 処理の締め切りを伝達
    • deadline
  • キャンセル信号の伝播
    • doneとか?
  • リクエストスコープ値の伝達

context定義

  • contextは下記のメソッドを持っている
  • 複数のgoroutineに ctxを渡してるので、そのgoroutineでも制御できる
type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

キャンセル


  // 実行
	ctx, cancel := context.WithCancel(context.Background())
    cancel()

  // 処理を止める
<-ctx.Done():
  • Doneは空構造体の受信専用チャネルを受け取る

type Context interface {
	Done() <-chan struct{}
	// (以下略)
}
  • 初期化
    • context.WithCancel, context.Background() のcontextはパッケージで、パッケージの関数を実行してるのか
    • Contextの実態を返している
ctx, cancel := context.WithCancel(context.Background())

chapter 04 親子構造のcontext

struct{}は?

フィールドを持たない構造体
ゼロ値は{}
サイズは0バイト=> どこにも保存していない

下記のように使う場合も、boolだとメモリを消費するけど、下記だとメモリを消費しないで存在チェックできる

set := map[string]struct{}{
    "apple":  {},
    "banana": {},
}

// 存在確認
if _, exists := set["apple"]; exists {
    fmt.Println("apple is in the set")
}

シグナル通知

ほしいのはチャネルの機能だけ、でもデータ型を指定しないといけないから、struct{}を使うのか

done := make(chan struct{})

go func() {
    fmt.Println("Processing...")
    close(done) // シグナル送信
}()

<-done // 受信(データなし)
fmt.Println("Finished")
engineer rebornengineer reborn

context.Background

apiの開発で、context.Backgroundのは、自分でnet/httpを作るときは、context.Backgroundを自分でやりそう。
echoなどのフレームワークを使ってる場合は、カスタマイズしたモノを提供してくれている

engineer rebornengineer reborn

まとめ

  • contextは親子関係で作成される
    • emptyCtx, vlaueCtxみたいな感じで
  • 値の受け渡しは、contextの構造体で受け渡しをしている
  • キャンセル処理でチャネルを利用してる

context のコードを読んでみる

  • Backgroundはビルド関数
    • backgroundCtxを返却で Context interfaceを満たす
## Background
	// Background、ビルド関数 は Context interfaceを返却する
	// struct{}を持つ
	ctx, cancal := context.WithCancel(context.Background())
  • WithCancel
    • 書き方がなるほどって感じ
    • 内部のメソッドは withCancel
    • cancelCtx を返却
      • 構造体 withCancel
    • cancel の仕組み
      • チャネルは送信されないと受信できない
      • チャネルは close されると受信にはゼロ値を返す
      • cancel は内部で close をしている(チャネルを受信できない状態にするため)

// WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := withCancel(parent)
	return c, func() { c.cancel(true, Canceled, nil) }
}

// propagateCancelはセッターかな

func withCancel(parent Context) *cancelCtx {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	c := &cancelCtx{}
	c.propagateCancel(parent, c)
	return c
}

// Done <- chan struct{}

// 下記の感じで
ctx, cancel := context.WithCancel(context.Background())

go func() {
    <-ctx.Done() // ここでキャンセルされるのを待つ(受信)
    fmt.Println("キャンセルされた!")
}()

// どこかで
cancel() // 

// 噛み砕くと下記らしい
// チャンネルは閉じられるとゼロ値を受信する
done := make(chan struct{})

go func() {
    <-done // ここで待つ
    fmt.Println("キャンセルされた!")
}()

// どこかで
close(done) // ← 

WithValue

type valueCtx struct {
	Context // parentが入る
	key, val any
}

## 親子構造
ctx := context.Background()
ctx1 := context.WithValue(ctx, "user", "alice")
ctx2 := context.WithValue(ctx1, "trace_id", "abc123")
ctx3 := context.WithValue(ctx2, "hello_id", "xyz999")

// フロー
ctx3: valueCtx (key: hello_id, val: "xyz999")
  ↓
ctx2: valueCtx (key: trace_id, val: "abc123")
  ↓
ctx1: valueCtx (key: user, val: "alice")
  ↓
ctx: emptyCtx (Background)

withCancel

  • 注意
    • キャンセルは親が子供に伝播するけど、子から親には伝播はしない
ctx := context.Background()
ctx1 := context.WithValue(ctx, "user", "alice")
ctx2, cancel := context.WithCancel(ctx1)
ctx3 := context.WithValue(ctx2, "trace_id", "abc123")
ctx4 := context.WithValue(ctx3, "hello_id", "xyz999")

ctx4: valueCtx (key: hello_id)
  ↓
ctx3: valueCtx (key: trace_id)
  ↓
ctx2: cancelCtx
  ↓
ctx1: valueCtx (key: user)
  ↓
ctx: emptyCtx (Background)

channel

  • チャネルは、FIFOでキューの実装
// int
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3

// user
type User struct {
    Name string
}

ch := make(chan User, 2)
ch <- User{Name: "Alice"}
ch <- User{Name: "Bob"}
  • ブロック
    • runtime、goparkで止める
engineer rebornengineer reborn

context をgromなどに渡した場合

  • contextを連携する
ctx := context.WithTimeout(context.Background(), 3*time.Second)
db := gormDB.WithContext(ctx)
db.Find(&users)
  • キャンセルを送る
    • API クライアントの接続が切れた場合
      • ex) ブラウザを閉じられたりする場合
        • 求人検索画面でリクエストを投げられても、クエリが重くブラウザを閉じられた場合は、クエリのキャンセルをしてくれる
        • 詳細
          • TCP コネクションが閉じる
          • Go の net/http サーバーはこの切断を検知
          • r.Context() がキャンセル
    • 親からキャンセル処理が送られた場合