Edited at

goroutineで大食い大会


はじめに

ゴルーチンとチャネルの勉強として大食い大会を書いてみた。

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は等しく選択されていく。なので、結果は毎回、拮抗した感じになる。