Help us understand the problem. What is going on with this article?

goroutineで大食い大会

More than 1 year has passed since last update.

はじめに

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

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

pokotyan
admin-guild
「Webサービスの運営に必要なあらゆる知見」を共有できる場として作られた、運営者のためのコミュニティです。
https://admin-guild.slack.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away