0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Goで学ぶコンテキストと同期プリミティブの活用方法

Posted at

はじめに

Go言語では、並行処理を制御するためのツールとして、コンテキスト(context)と同期プリミティブが提供されています。本記事では、これらの活用方法を基礎から解説し、実践的な例を紹介します。

コンテキスト(context)とは?

コンテキスト(context)は、Goの標準ライブラリで提供される機能で、並行処理における「キャンセル」「タイムアウト」「値の伝播」を管理するために使用されます。

典型的なシナリオ:

  • HTTPリクエストがキャンセルされたときに、関連するgoroutineを停止させる。
  • 一定時間内に処理を完了させるタイムアウト制御。

コンテキストの基本操作

package main

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

func main() {
	// タイムアウト付きのコンテキストを作成
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel() // 必ずキャンセル関数を呼ぶ

	// goroutineで処理を実行
	go func(ctx context.Context) {
		for {
			select {
			case <-ctx.Done(): // タイムアウト時の処理
				fmt.Println("Context canceled:", ctx.Err())
				return
			default:
				fmt.Println("Working...")
				time.Sleep(500 * time.Millisecond)
			}
		}
	}(ctx)

	// メイン関数で待機
	time.Sleep(3 * time.Second)
	fmt.Println("Main function finished")
}

このコードでは、2秒後にタイムアウトが発生し、goroutineが終了します。context.WithTimeoutを利用することで、簡単にタイムアウト制御が可能です。

コンテキストのキャンセル操作

手動でキャンセルを実行する場合には、context.WithCancelを使用します。

package main

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

func main() {
	// キャンセル可能なコンテキストを作成
	ctx, cancel := context.WithCancel(context.Background())

	// goroutineを起動
	go func(ctx context.Context) {
		for {
			select {
			case <-ctx.Done():
				fmt.Println("Canceled")
				return
			default:
				fmt.Println("Running...")
				time.Sleep(500 * time.Millisecond)
			}
		}
	}(ctx)

	// 2秒後にキャンセル
	time.Sleep(2 * time.Second)
	cancel()
	time.Sleep(1 * time.Second)
}

同期プリミティブとは?

Goの同期プリミティブは、複数のgoroutine間でデータを安全に共有するために使用されます。主な同期プリミティブには以下があります:

  • sync.Mutex(ミューテックス)
  • sync.WaitGroup(ウェイトグループ)
  • sync.Once(一度だけ実行)
  • sync.Cond(条件変数)

Mutex(ミューテックス)の使い方

sync.Mutexは、共有データへの同時アクセスを防ぐために使用します。

package main

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

func main() {
	var mu sync.Mutex
	counter := 0

	increment := func() {
		mu.Lock() // ロック
		defer mu.Unlock() // 処理終了後にアンロック
		counter++
		fmt.Println("Counter:", counter)
	}

	// 複数のgoroutineで実行
	for i := 0; i < 5; i++ {
		go increment()
	}

	time.Sleep(1 * time.Second)
}

この例では、mu.Lockmu.Unlockで保護することで、counterに安全にアクセスできます。

WaitGroup(ウェイトグループ)の使い方

sync.WaitGroupは、複数のgoroutineが終了するのを待機するために使用されます。

package main

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

func main() {
	var wg sync.WaitGroup

	worker := func(id int) {
		defer wg.Done() // 作業終了を通知
		fmt.Printf("Worker %d starting\n", id)
		time.Sleep(time.Second) // 処理に1秒
		fmt.Printf("Worker %d done\n", id)
	}

	// 3つのgoroutineを起動
	for i := 1; i <= 3; i++ {
		wg.Add(1) // goroutineを追加
		go worker(i)
	}

	wg.Wait() // すべてのgoroutineが終了するのを待つ
	fmt.Println("All workers finished")
}

Once(一度だけ実行)の使い方

sync.Onceを使うと、処理を一度だけ実行することを保証できます。

package main

import (
	"fmt"
	"sync"
)

func main() {
	var once sync.Once

	init := func() {
		fmt.Println("Initialization")
	}

	for i := 0; i < 3; i++ {
		go func() {
			once.Do(init) // 一度だけ実行
		}()
	}

	fmt.Scanln() // 終了を防ぐ
}

まとめ

Go言語のコンテキスト(context)と同期プリミティブを活用することで、複雑な並行処理をシンプルに実現できます。

  • コンテキストはタイムアウトやキャンセル処理に便利。
  • 同期プリミティブはデータの安全な共有やgoroutineの調整に役立つ。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?