goroutineで大食い大会

# はじめに

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

1)コックはひたすらホットドッグを作る
2)ウェイターはひたすらホットドッグをフードファイターへ運ぶ
3)フードファイターはひたすらホットドッグを食べて、自分が食べたホットドッグをカウントする
4）制限時間になったら大会終了。その時点でのランキングを出す。

この 1 => 2 => 3 の流れをチャネルを使い、4の制限時間の制御にcontextを使った。

# コード

```golang

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


[^1]: [Go言語による並行処理](https://www.amazon.co.jp/Go%E8%A8%80%E8%AA%9E%E3%81%AB%E3%82%88%E3%82%8B%E4%B8%A6%E8%A1%8C%E5%87%A6%E7%90%86-Katherine-Cox-Buday/dp/4873118468/ref=sr_1_1?adgrpid=53656810552&gclid=CjwKCAjwqZPrBRBnEiwAmNJsNpBbuTJO1I4_oHiCv7-DPff6txXhDuqASJP83PQLzZE70XJIayLeYBoCAdoQAvD_BwE&hvadid=338517649830&hvdev=c&hvlocphy=1009308&hvnetw=g&hvpos=1t1&hvqmt=e&hvrand=7117459373456386200&hvtargid=aud-759377471893%3Akwd-569502270888&hydadcr=27264_11561112&jp-ad-ap=0&keywords=go%E8%A8%80%E8%AA%9E%E3%81%AB%E3%82%88%E3%82%8B%E4%B8%A6%E8%A1%8C%E5%87%A6%E7%90%86&qid=1566899081&s=gateway&sr=8-1) p81