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

Go言語の並行処理デザインパターン by Rob Pike 前編

More than 1 year has passed since last update.

少し古いですが、Rob Pikeの並行処理デザインパターンのビデオで取り上げられたコードまとめです。
オリジナルのソースコードはこちらで見れます。

Generator ジェネレータ

generator.go
package main

import (
    "fmt"
    "math/rand"
    "time"
    "runtime"
)

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    rand.Seed(time.Now().UnixNano())

    c := boring("boring!") // Function returning a channel.
    for i := 0; i < 5; i++ {
        fmt.Printf("You say: %q\n", <-c)
    }
    fmt.Println("You're boring: I'm leaving.")
}

func boring(msg string) <-chan string {
    c := make(chan string)
    go func() { // We launch the goroutine from inside the function.
        for i := 0; ; i++ {
            c <- fmt.Sprintf("%s %d", msg, i)
            time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
        }

    }()
    return c // Return the channel to the caller.
}

channelはファーストクラスオブジェクトなので、関数の引数や、戻り値に使用できます。
boring関数内でチャネルを作り、goルーチンを使用し、for文の無限ループ内で、メイン関数に送信しています。
チャネルは同期処理なので、メイン側で受信されるまで、boring関数はブロックされます。
boring関数から、チャネルを返却し、メイン関数側へのコールバックに使用します。
乱数で一定時間wait処理を入れ、送受信が同期で処理されているのを分かりやすくしてます。


generator2.go
func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    rand.Seed(time.Now().UnixNano())

    joe := boring("Joe") // Function returning a channel.
    ann := boring("Ann") // Function returning a channel.
    for i := 0; i < 5; i++ {
        fmt.Printf("You say: %q\n", <-joe)
        fmt.Printf("You say: %q\n", <-ann)
    }
    fmt.Println("You're boring: I'm leaving.")
}

func boring(msg string) <-chan string {
// 同じなので省略

この例では、boring関数を2回呼び出し、2つのチャネルを使用しています。boring関数をサービスとして使用しているとのことです。
JoeとAnnがペアで交互に実行されます。Annの方が先に処理を終えても、Joeの処理を待ちます。
次の例では、独立して実行させてみます。


Multiplexing マルチプレクサ

multiplexing.go
func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    rand.Seed(time.Now().UnixNano())

    c := fanIn(boring("Joe"), boring("Ann"))
    for i := 0; i < 10; i++ {
        fmt.Printf("You say: %q\n", <-c)
    }
    fmt.Println("You're boring: I'm leaving.")
}

func fanIn(input1, input2 <-chan string) <-chan string {
    c := make(chan string)
    go func() {
        for {
            c <- <-input1
        }
    }()
    go func() {
        for {
            c <- <-input2
        }
    }()
    return c
}

func boring(msg string) <-chan string {
// 同じなので省略

boring関数のチャネルを2つ引数に取るfanIn関数を定義してます。
内部でgoルーチンを使用して、それぞれ別のgoルーチンで独立して処理するようにしています。
これにより、先に処理が終わった方から、共通のチャネルcに送信するため、JoeとAnnが独立して実行されます。

出力結果

You say: "Joe 0"
You say: "Ann 0"
You say: "Joe 1"
You say: "Ann 1"
You say: "Ann 2"
You say: "Ann 3"
You say: "Ann 4"
You say: "Joe 2"
You say: "Joe 3"
You say: "Ann 5"
You're boring: I'm leaving.

Process finished with exit code 0

Select セレクト

select.go
func fanIn(input1, input2 <-chan string) <-chan string {
    c := make(chan string)

    go func() {
        for {
            select {
            case s := <-input1:
                c <- s
            case s := <-input2:
                c <- s
            }
        }
    }()

    return c
}

先ほどのMultiplexingを、selectで実装したものです。同じ動きになります。
goルーチン1つですっきりと書けます。


Quit? 停止?

quit.go
func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    rand.Seed(time.Now().UnixNano())

    quit := make(chan bool)
    c := boring("Joe", quit)

    for i := rand.Intn(10); i>=0; i-- {
        fmt.Println(<-c)
    }

    quit <- true // send a quit signal after a certain amount of time.
}

func boring(msg string, quit <-chan bool) <-chan string {
    c := make(chan string)
    go func() { // We launch the goroutine from inside the function.
        for i := 0; ; i++ {
            select {
            case c <- fmt.Sprintf("%s %d", msg, i):
            case <-quit:
                return // exit the for-loop.
            }
        }
    }()
    return c // Return the channel to the caller.
}

メイン関数からチャネルをboring関数に渡して、メイン関数からシグナルを送り、boring関数を停止させる例です。
もっともこの例では、メイン関数もすぐに終了してしまうので、次の例にて。。。


RoundTrip ラウンドトリップ

cleanup.go
func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    rand.Seed(time.Now().UnixNano())

    quit := make(chan bool)
    c := boring("Joe", quit)

    for i := rand.Intn(10); i >= 0; i-- {
        fmt.Println(<-c)
    }

    quit <- true // send a quit signal after a certain amount of time.

    fmt.Printf("Joe is done. %t", <-quit)
}

func boring(msg string, quit chan bool) <-chan string {
    c := make(chan string)
    go func() { // We launch the goroutine from inside the function.
        for i := 0; ; i++ {
            select {
            case c <- fmt.Sprintf("%s %d", msg, i):
            case <-quit:
                cleanup()
                quit <- true
                return // exit the for-loop.
            }
        }
    }()
    return c // Return the channel to the caller.
}

func cleanup() {
    // do clean up tasks...
}

quitチャネルを双方向通信で使用します。boring関数にて、リソースのクリーンナップ処理cleanup()をしてから、メイン関数にコールバックしてます。

後編はこちら

tfutada
シリコンバレー、ニューヨーク(NTTデータUSA)、インド(マヒンドラサティヤム)、ベトナム(フリー)を経て、現在は東南アジアのオフショア開発のコンサルティング会社経営 Swift Kotlin Python Go TS 英語(TOEIC895)、中国語(ニイハオレベル)。語学は発音がすべて。
https://note.mu/tafutafu
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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした