同期プリミティブ

Go 言語げんご同期どうきプリミティブ:Mutex、WaitGroup、Atomic。

WaitGroups

起動きどうしたすべての Goroutines完了かんりょうつために使用しよう

goroutine からデータをかえ必要ひつようがない場合ばあいは、WaitGroups を使用しよう

  • 複数ふくすうの goroutine がたがいをつことを可能かのうにする同期どうきプリミティブ
  • 内部ないぶカウンタが 0 になるまで実行じっこうをブロック
  • package sync基本きほん同期どうき機能きのう提供ていきょう
import "sync"

var wg sync.WaitGroup

wg.Add(int)

wg.Wait()

wg.Done()
WaitGroup を関数かんすう明示的めいじてきわた場合ばあいは、ポインタでわたすべき。

Methods

  • Addつ goroutine のかず設定せってい(カウンタを増加ぞうか

    • 引数ひきすう整数せいすうがカウンタとして機能きのう
    • つ goroutine のかずしめ
  • Wait内部ないぶカウンタが 0 になるまで実行じっこうをブロック

  • DoneAdd() メソッドの内部ないぶカウントを 1 減少げんしょう

    • goroutine が完了かんりょうしたことをしめすために
Go WaitGroup

完全な例

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            fmt.Printf("Worker %d done\n", id)
        }(i)
    }

    wg.Wait()
    fmt.Println("All workers completed")
}

Mutexes

ミューテックス、共有きょうゆうリソースを保護ほごし、同時どうじに 1 つの goroutine だけがアクセスできることを保証ほしょう

sync.Mutex

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.value++
}

func (c *Counter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

sync.RWMutex

きロック、複数ふくすうりを同時どうじ許可きょかするが、みは排他的はいたてき

メソッド説明せつめい
RLock()りロックを取得しゅとく複数ふくすう同時どうじ保持ほじ可能かのう
RUnlock()りロックを解放かいほう
Lock()みロックを取得しゅとく排他的はいたてき
Unlock()みロックを解放かいほう
type SafeMap struct {
    mu   sync.RWMutex
    data map[string]int
}

func (m *SafeMap) Get(key string) (int, bool) {
    m.mu.RLock()
    defer m.mu.RUnlock()
    val, ok := m.data[key]
    return val, ok
}

func (m *SafeMap) Set(key string, value int) {
    m.mu.Lock()
    defer m.mu.Unlock()
    m.data[key] = value
}

Atomic Counters

アトミックカウンタ、ロックフリーのカウント操作そうさ使用しよう、Mutex より軽量けいりょう

import "sync/atomic"

var counter int64

// アトミックに増加
atomic.AddInt64(&counter, 1)

// アトミックに読み取り
value := atomic.LoadInt64(&counter)

// アトミックに保存
atomic.StoreInt64(&counter, 100)

// Compare-And-Swap
atomic.CompareAndSwapInt64(&counter, old, new)

完全な例

func main() {
    var counter int64
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            atomic.AddInt64(&counter, 1)
        }()
    }

    wg.Wait()
    fmt.Println("Counter:", counter) // 1000
}

sync.Once

関数かんすうが 1 かいだけ実行じっこうされることを保証ほしょう、シングルトンパターンや初期化しょきかによく使つかわれる。

var (
    instance *Database
    once     sync.Once
)

func GetInstance() *Database {
    once.Do(func() {
        instance = &Database{
            // 初期化...
        }
    })
    return instance
}

sync.Pool

オブジェクトプール、一時いちじオブジェクトを再利用さいりようして、メモリてと GC の負荷ふか軽減けいげん

var bufferPool = sync.Pool{
    New: func() interface{} {
        return new(bytes.Buffer)
    },
}

func process(data []byte) {
    buf := bufferPool.Get().(*bytes.Buffer)
    defer func() {
        buf.Reset()
        bufferPool.Put(buf)
    }()

    buf.Write(data)
    // buffer を処理...
}

sync.Map

並行へいこう安全あんぜんな map、りがおおみがすくないシナリオにてきしている。

var m sync.Map

// 保存
m.Store("key", "value")

// 読み取り
value, ok := m.Load("key")

// 読み取りまたは保存(存在しない場合は保存)
actual, loaded := m.LoadOrStore("key", "default")

// 削除
m.Delete("key")

// 反復
m.Range(func(key, value interface{}) bool {
    fmt.Printf("%v: %v\n", key, value)
    return true // false を返すと反復を停止
})
ほとんどの場合ばあいmap + sync.RWMutexほうがパフォーマンスがい。sync.Map は key 集合しゅうごう安定あんていしていてりが頻繁ひんぱんなシナリオにてきしている。

Worker Pools

ワーカープールパターン、並行へいこうタスクを管理かんりし、並行へいこうすう制御せいぎょ

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        results <- job * 2
    }
}

func main() {
    const numJobs = 10
    const numWorkers = 3

    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)

    // ワーカーを起動
    for w := 1; w <= numWorkers; w++ {
        go worker(w, jobs, results)
    }

    // タスクを送信
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)

    // 結果を収集
    for r := 1; r <= numJobs; r++ {
        <-results
    }
}

関連トピック