はじめに
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をうまく使って、効率的で柔軟なプログラムを作成しましょう。