サーチ…
前書き
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.Sleep
はmain()
関数が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を使用する:
- グローバル変数の宣言。それをグローバルにすることは、すべての関数とメソッドでそれを見えるようにする最も簡単な方法です。
- カウンターを増やす。これはメイン・ゴルーチンで行う必要があります。これは、メモリー・モデルの保証により、新しく開始されたゴルーチンが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でこのコードをわずかに修正したバージョンを実行する