サーチ…


前書き

Goでは、同時実行はゴルーチンの使用によって達成され、ゴルーチン間の通信は通常チャネルで行われます。しかし、mutexやwaitグループのような他の同期手段が利用可能であり、チャネルよりも便利なときはいつでも使用してください。

構文

  • go doWork()// doWorkをゴルーチンとして実行する
  • ch:= make(chan int)// int型の新しいチャンネルを宣言する
  • ch < - 1 //チャンネルで送信する
  • value = <-ch //チャンネルから受信する

備考

Goのゴルーチンは、他の言語のスレッドと同じように使用されます。内部的には、Goはいくつかのスレッド( GOMAXPROCS指定)を作成し、スレッド上で実行するようにゴルーチンをスケジュールします。この設計のために、Goの並行処理メカニズムは、メモリ使用量と初期化時間に関してスレッドよりもはるかに効率的です。

ゴルーチンを作成する

任意の関数は、その呼び出しの前にキーワードgoを接頭辞として付けることで、ゴルーチンとして呼び出すことができます。

func DoMultiply(x,y int) {
    // Simulate some hard work
    time.Sleep(time.Second * 1)
    fmt.Printf("Result: %d\n", x * y)
}

go DoMultiply(1,2) // first execution, non-blocking
go DoMultiply(3,4) // second execution, also non-blocking

// Results are printed after a single second only, 
// not 2 seconds because they execute concurrently:
// Result: 2
// Result: 12

関数の戻り値は無視されることに注意してください。

Hello World Goroutine

シングルチャンネル、シングルゴルーチン、1つの書き込み、1つの読み取り。

package main

import "fmt"
import "time"

func main() {
    // create new channel of type string
    ch := make(chan string)

    // start new anonymous goroutine
    go func() {
        time.Sleep(time.Second)
        // send "Hello World" to channel
        ch <- "Hello World"
    }()
    // read from channel
    msg, ok := <-ch
    fmt.Printf("msg='%s', ok='%v'\n", msg, ok)
}

遊び場で走る

チャネルchは、 バッファなしまたは同期チャネルです。

time.Sleepmain()関数がchチャネルで待機することを示しています。つまり、goroutineとして実行される関数リテラルには、そのチャネルを通じて値を送信する時間があります。 受信オペレータ<-chは、 main() 。そうでなければ、 main()が終了するとゴルーチンが殺され、その値を送る時間はありません。

ゴルーチンを待っている

ゴープログラムは、 main関数が終了すると終了します 。したがって、すべてのゴルーチンが終了するのを待つのが一般的な方法です。これに対する共通の解決策は、 sync.WaitGroupオブジェクトを使用することです。

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup // 1

func routine(i int) {
    defer wg.Done() // 3
    fmt.Printf("routine %v finished\n", i)
}

func main() {
    wg.Add(10) // 2
    for i := 0; i < 10; i++ {
        go routine(i) // *
    }
    wg.Wait() // 4
    fmt.Println("main finished")
}

このサンプルをプレイグラウンドで実行する

実行順にWaitGroupを使用する:

  1. グローバル変数の宣言。それをグローバルにすることは、すべての関数とメソッドでそれを見えるようにする最も簡単な方法です。
  2. カウンターを増やす。これはメイン・ゴルーチンで行う必要があります。これは、メモリー・モデルの保証により、新しく開始されたゴルーチンが4より前に実行されるという保証がないためです。
  3. カウンターを減らす。これは、ゴルーチンの出口で行う必要があります。遅延呼び出しを使用することにより、 関数の終了時に関数が終了するたびに呼び出されること確認します。
  4. カウンタが0になるのを待つ。これは、すべてのゴルーチンが終了する前にプログラムが終了しないように、メインのゴルーチンで行わなければならない。

*パラメータは新しいゴルーチンを開始する前に評価されます。したがって、おそらくパニックコードがカウンタを増加させないように、 wg.Add(10)前にその値を明示的に定義する必要があります。前に10の項目を待機するので、WaitGroupに10の項目を追加wg.Waitバックに制御を返すmain()ゴルーチン。ここで、iの値はforループで定義されます。

ループ内のゴルーチンでクロージャを使用する

ループ内では、次の例のループ変数(val)は、ループを通過するときに値を変更する単一の変数です。したがって、実際に値の各値をゴルーチンに渡すには、次の処理を実行する必要があります。

for val := range values {
    go func(val interface{}) {
        fmt.Println(val)
    }(val)
}

func(val interface{}) { ... }()を渡さずにfunc(val interface{}) { ... }()を実行するだけであれば、 valの値はgoroutineが実際に実行されるときのvalとなります。

同じ効果を得る別の方法は次のとおりです。

for val := range values {
    val := val
    go func() {
        fmt.Println(val)
    }()
}

変わったval := valは、各繰り返しで新しい変数を作成し、その後、ゴルーチンによってアクセスされます。

ゴルーチンの停止

package main

import (
    "log"
    "sync"
    "time"
)

func main() {
    // The WaitGroup lets the main goroutine wait for all other goroutines
    // to terminate. However, this is no implicit in Go. The WaitGroup must
    // be explicitely incremented prior to the execution of any goroutine 
    // (i.e. before the `go` keyword) and it must be decremented by calling
    // wg.Done() at the end of every goroutine (typically via the `defer` keyword). 
    wg := sync.WaitGroup{}

    // The stop channel is an unbuffered channel that is closed when the main
    // thread wants all other goroutines to terminate (there is no way to 
    // interrupt another goroutine in Go). Each goroutine must multiplex its
    // work with the stop channel to guarantee liveness.
    stopCh := make(chan struct{})


    for i := 0; i < 5; i++ {
        // It is important that the WaitGroup is incremented before we start
        // the goroutine (and not within the goroutine) because the scheduler
        // makes no guarantee that the goroutine starts execution prior to 
        // the main goroutine calling wg.Wait().
        wg.Add(1)
        go func(i int, stopCh <-chan struct{}) {
            // The defer keyword guarantees that the WaitGroup count is 
            // decremented when the goroutine exits.
            defer wg.Done()

            log.Printf("started goroutine %d", i)

            select {
            // Since we never send empty structs on this channel we can 
            // take the return of a receive on the channel to mean that the
            // channel has been closed (recall that receive never blocks on
            // closed channels).   
            case <-stopCh:
                log.Printf("stopped goroutine %d", i)
            }
        }(i, stopCh)
    }

    time.Sleep(time.Second * 5)
    close(stopCh)
    log.Printf("stopping goroutines")
    wg.Wait()
    log.Printf("all goroutines stopped")
}

2つのゴルーチンがあるピンポン

package main

import (
    "fmt"
    "time"
)

// The pinger prints a ping and waits for a pong
func pinger(pinger <-chan int, ponger chan<- int) {
    for {
        <-pinger
        fmt.Println("ping")
        time.Sleep(time.Second)
        ponger <- 1
    }
}

// The ponger prints a pong and waits for a ping
func ponger(pinger chan<- int, ponger <-chan int) {
    for {
        <-ponger
        fmt.Println("pong")
        time.Sleep(time.Second)
        pinger <- 1
    }
}

func main() {
    ping := make(chan int)
    pong := make(chan int)

    go pinger(ping, pong)
    go ponger(ping, pong)

    // The main goroutine starts the ping/pong by sending into the ping channel
    ping <- 1

    for {
        // Block the main thread until an interrupt
        time.Sleep(time.Second)
    }
}

Go Playgroundでこのコードをわずかに修正したバージョンを実行する



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow