いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-499 (シンプルなゲート)(Gate, カウント1のラッチ)

関連記事

Goメモ-498 (シンプルなラッチ)(CountdownLatch, CountdownEvent) - いろいろ備忘録日記

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

前回、ラッチについてのサンプルをメモしたので、ついでにゲートもメモ。

カウント1のラッチは、単純なオン/オフのラッチとなり、ゲートとして機能します。

サンプル

countdownlatch.go

以下を参照ください。

Goメモ-498 (シンプルなラッチ)(CountdownLatch, CountdownEvent) - いろいろ備忘録日記

gate.go

package main

type (
    Gate struct {
        latch *CountdownLatch
    }
)

func NewGate() *Gate {
    var (
        latch = NewCountdownLatch(1)
        gate  = Gate{latch}
    )

    return &gate
}

func (me *Gate) Await() {
    if me.latch.CurrentCount() < 1 {
        return
    }

    me.latch.Wait()
}

func (me *Gate) Open() {
    if me.latch.CurrentCount() < 1 {
        return
    }

    me.latch.Signal()
}

func (me *Gate) Reset() {
    me.latch.Reset(1)
}

main.go

package main

import (
    "context"
    "errors"
    "log"
    "sync"
    "time"
)

const (
    MainTimeout = 20 * time.Second
    ProcTimeout = 10 * time.Second
)

var (
    ErrMainTooSlow = errors.New("(MAIN) TOO SLOW")
    ErrProcTooSlow = errors.New("(PROC) TOO SLOW")
)

func init() {
    log.SetFlags(0)
}

func main() {
    var (
        rootCtx          = context.Background()
        mainCtx, mainCxl = context.WithTimeoutCause(rootCtx, MainTimeout, ErrMainTooSlow)
        procCtx          = run(mainCtx)
        err              error
    )
    defer mainCxl()

    select {
    case <-mainCtx.Done():
        err = context.Cause(mainCtx)
    case <-procCtx.Done():
        if err = context.Cause(procCtx); errors.Is(err, context.Canceled) {
            err = nil
        }
    }

    if err != nil {
        log.Fatal(err)
    }
}

func run(pCtx context.Context) context.Context {
    var (
        ctx, cxl = context.WithCancelCause(pCtx)
    )

    go func() {
        cxl(proc(ctx))
    }()
    go func() {
        <-time.After(ProcTimeout)
        cxl(ErrProcTooSlow)
    }()

    return ctx
}

func proc(_ context.Context) error {
    var (
        gate = NewGate()
    )
    for range 2 {
        var (
            wg sync.WaitGroup
        )

        gate.Reset()

        // 10個のゴルーチンがゲート前に待機する
        for i := range 10 {
            wg.Add(1)
            go func(i int) {
                defer wg.Done()

                log.Printf("[%2d] 待機開始", i)
                gate.Await()
                log.Printf("[%2d] 待機解除", i)
            }(i)
        }

        // 何か準備処理などを行っているとする
        <-time.After(time.Second)
        log.Println("-------------------------------------")

        // ゲートを開き、待機解除したゴルーチン達が全完了するのを待つ
        gate.Open()
        wg.Wait()
        log.Println("*************************************")
    }

    return nil
}

Taskfile.yml

# https://taskfile.dev

version: '3'

tasks:
  default:
    cmds:
      - go run .

実行

$ task
[ 4] 待機開始
[ 8] 待機開始
[ 2] 待機開始
[ 3] 待機開始
[ 1] 待機開始
[ 0] 待機開始
[ 5] 待機開始
[ 6] 待機開始
[ 7] 待機開始
[ 9] 待機開始
-------------------------------------
[ 9] 待機解除
[ 4] 待機解除
[ 8] 待機解除
[ 5] 待機解除
[ 0] 待機解除
[ 1] 待機解除
[ 6] 待機解除
[ 2] 待機解除
[ 7] 待機解除
[ 3] 待機解除
*************************************
[ 9] 待機開始
[ 0] 待機開始
[ 7] 待機開始
[ 8] 待機開始
[ 4] 待機開始
[ 5] 待機開始
[ 2] 待機開始
[ 6] 待機開始
[ 1] 待機開始
[ 3] 待機開始
-------------------------------------
[ 3] 待機解除
[ 9] 待機解除
[ 5] 待機解除
[ 4] 待機解除
[ 1] 待機解除
[ 0] 待機解除
[ 7] 待機解除
[ 8] 待機解除
[ 6] 待機解除
[ 2] 待機解除
*************************************

門の前に10個のゴルーチンが集まって待機していて、門が開いたら全員一斉に駆け出して、また門が閉まって・・・って感じですね。

TDLやUSJの開園前みたいなイメージ。

参考情報

github.com

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。