LoginSignup
138
131

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-06-02

少し古いですが、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()をしてから、メイン関数にコールバックしてます。

後編はこちら

138
131
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
138
131