はじめに
ゴルーチンとチャネルの勉強として大食い大会を書いてみた。
1)コックはひたすらホットドッグを作る
2)ウェイターはひたすらホットドッグをフードファイターへ運ぶ
3)フードファイターはひたすらホットドッグを食べて、自分が食べたホットドッグをカウントする
4)制限時間になったら大会終了。その時点でのランキングを出す。
この 1 => 2 => 3 の流れをチャネルを使い、4の制限時間の制御にcontextを使った。
コード
package main
import (
"context"
"fmt"
"time"
. "github.com/ahmetb/go-linq"
)
func cook(
done <-chan interface{},
fn func() interface{},
) <-chan interface{} {
foods := make(chan interface{})
go func() {
defer close(foods)
for {
select {
case <-done:
return
case foods <- fn():
}
}
}()
return foods
}
func hotDog() interface{} {
fmt.Printf("コックはホットドッグを作ります\n")
return "ホットドッグ"
}
func waiter(foods <-chan interface{}) interface{} {
food := <-foods
fmt.Printf("ウェイターは%vをフードファイターへ持っていきます。\n", food)
return food
}
func waitperson(
done <-chan interface{},
foods <-chan interface{},
) <-chan interface{} {
toFoodFighter := make(chan interface{})
go func() {
defer close(toFoodFighter)
for {
select {
case <-done:
return
case toFoodFighter <- waiter(foods):
}
}
}()
return toFoodFighter
}
type FoodFighter struct {
name string
current int
}
type FoodFighters []*FoodFighter
func (f *FoodFighter) eat(foods <-chan interface{}) *FoodFighter {
food := <-foods
f.current++
fmt.Printf("%vは%vを%v個食べた\n", f.name, food, f.current)
return f
}
func (f *FoodFighter) startEat(
done <-chan interface{},
foods <-chan interface{},
) <-chan *FoodFighter {
results := make(chan *FoodFighter)
go func() {
defer close(results)
for {
select {
case <-done:
return
case results <- f.eat(foods):
}
}
}()
return results
}
func start(done <-chan interface{}, f *FoodFighter) <-chan *FoodFighter {
return f.startEat(done, waitperson(done, cook(done, hotDog)))
}
func competition(ctx context.Context, cancel context.CancelFunc, done <-chan interface{}) FoodFighters {
defer cancel()
var foodFighters FoodFighters
var f1Result *FoodFighter
var f2Result *FoodFighter
var f3Result *FoodFighter
f1 := &FoodFighter{name: "ボブ"}
f2 := &FoodFighter{name: "ビル"}
f3 := &FoodFighter{name: "マイケル"}
for {
select {
case f1Result = <-start(done, f1):
case f2Result = <-start(done, f2):
case f3Result = <-start(done, f3):
case <-ctx.Done():
fmt.Println("大会終了です\n")
foodFighters = append(foodFighters, f1Result)
foodFighters = append(foodFighters, f2Result)
foodFighters = append(foodFighters, f3Result)
return foodFighters
}
}
}
func displayResult(results FoodFighters) {
time.Sleep(1 * time.Second)
fmt.Println("大会結果発表\n")
sortedResults := From(results).Sort(func(f1, f2 interface{}) bool {
return f1.(*FoodFighter).current > f2.(*FoodFighter).current
}).Results()
for i, fighter := range sortedResults {
fmt.Printf("%v位: %v %v個\n", i+1, fighter.(*FoodFighter).name, fighter.(*FoodFighter).current)
}
}
func foodFight() {
done := make(chan interface{})
defer close(done)
limitTime := 2 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), limitTime)
results := competition(ctx, cancel, done)
displayResult(results)
}
func main() {
foodFight()
}
実行結果
:
:
マイケルはホットドッグを417個食べた
ウェイターはホットドッグをフードファイターへ持っていきます。
ウェイターはホットドッグをフードファイターへ持っていきます。
コックはホットドッグを作ります
コックはホットドッグを作ります
ボブはホットドッグを447個食べた
コックはホットドッグを作ります
ビルはホットドッグを442個食べた
ウェイターはホットドッグをフードファイターへ持っていきます。
大会結果発表
1位: ボブ 447個
2位: ビル 442個
3位: マイケル 418個
goではselectのcase文全体に対して 擬似乱数による一様選択
1 をしているらしい。
それぞれのcaseは等しく選択されていく。なので、結果は毎回、拮抗した感じになる。
-
Go言語による並行処理 p81 ↩