LoginSignup
1
1

More than 1 year has passed since last update.

Go言語でContextを活用する3つのパターン

Posted at

はじめに

Go言語ではcontextパッケージを利用して、タイムアウトやキャンセル処理を実装できます。
下記でcontextを活用する3つのパターンを紹介します。

HTTPサーバーとHTTPクライアントのタイムアウトとキャンセル

net/httpパッケージを使って、HTTPリクエストにタイムアウトを設定し、リクエスト処理中にキャンセルが発生した場合に適切に処理できるようにします。

HTTPサーバー例

リクエストにタイムアウトを設定し、リクエスト処理中にキャンセルが発生した場合に適切に処理できるようにするHTTPサーバーの例です。

package main

import (
	"context"
	"fmt"
	"net/http"
	"time"
)

func main() {
	http.HandleFunc("/example", handleRequest)
	http.ListenAndServe(":8080", nil)
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
	defer cancel()

	result := make(chan string)

	go func() {
		time.Sleep(3 * time.Second)
		result <- "Success"
	}()

	select {
	case res := <-result:
		fmt.Fprintln(w, res)
	case <-ctx.Done():
		w.WriteHeader(http.StatusRequestTimeout)
		fmt.Fprintln(w, "Request timed out")
	}
}

HTTPクライアント例

HTTPクライアントでリクエストを送信する際に、タイムアウトを設定して、適切にキャンセル処理ができるようにする例です。

gopackage main

import (
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	req, err := http.NewRequest("GET", "http://localhost:8080/example", nil)
	if err != nil {
		panic(err)
	}

	req = req.WithContext(ctx)

	client := &http.Client{}
	resp, err := client.Do(req)

	if err != nil {
		fmt.Println("Request error:", err)
		return
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		panic(err)
	}

	fmt.Println("Response:", string(body))
}

タスクのキャンセル

タスクが進行中にキャンセルが発生した場合、すぐにタスクを中止する例です。

package main

import (
	"context"
	"fmt"
	"math/rand"
	"time"
)

func student(ctx context.Context, name string) {
	preparationTime := time.Duration(rand.Intn(5)+1) * time.Second
	fmt.Printf("%s は準備に %v かかります\n", name, preparationTime)

	select {
	case <-time.After(preparationTime):
		fmt.Printf("%s が準備を完了しました\n", name)
	case <-ctx.Done():
		fmt.Printf("%s は準備を中止しました\n", name)
	}
}

func main() {
	rand.Seed(time.Now().UnixNano())
	ctx, cancel := context.WithCancel(context.Background())

	names := []string{"Alice", "Bob", "Charlie", "David", "Eva"}
	for _, name := range names {
		go student(ctx, name)
	}

	time.Sleep(3 * time.Second)
	if rand.Intn(10) < 5 { // 50% の確率で悪天候
		fmt.Println("悪天候のためお祭りを中止")
		cancel()
	}

	time.Sleep(5 * time.Second)
}

最速のタスクの結果を使用する

複数のタスクを実行し、最初に完了したタスクの結果を受け取って、残りのタスクをキャンセルする例です。

package main

import (
	"context"
	"fmt"
	"math/rand"
	"sync"
	"time"
)

func performTask(ctx context.Context, taskID int, result chan<- int) {
	duration := time.Duration(rand.Intn(5)+1) * time.Second
	fmt.Printf("Task %d will take %v to complete\n", taskID, duration)

	select {
	case <-time.After(duration):
		select {
		case result <- taskID:
		case <-ctx.Done():
		}
	case <-ctx.Done():
		fmt.Printf("Task %d was cancelled\n", taskID)
	}
}

func main() {
	rand.Seed(time.Now().UnixNano())
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	result := make(chan int)
	var wg sync.WaitGroup

	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go func(taskID int) {
			defer wg.Done()
			performTask(ctx, taskID, result)
		}(i)
	}

	fastestTask := <-result
	fmt.Printf("Task %d completed first\n", fastestTask)

	cancel()
	wg.Wait()
}

これらのパターンは、タイムアウトやキャンセル処理が必要な様々なシーンで活用できます。contextをうまく使って、効率的で柔軟なプログラムを作成しましょう。

1
1
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
1
1