mutex を使い明示的なロックを取って、複数のゴルーチンで共有データへのアクセスを同期する方法は既に紹介した。 ゴルーチンを同期する組み込みの機能とチャネルを使っても、同じ結果が得られる。 チャネルを使うこのやり方は、各ゴルーチンが持つデータをやり取りしてメモリを共有する Go のアイデアに合っている。 |
|
![]() ![]() package main
|
|
import (
"fmt"
"math/rand"
"sync/atomic"
"time"
)
|
|
この例では、あるゴルーチンだけが状態を所有する。
この結果、アクセスの競合によってデータが壊れてしまうことなくなる。
状態を読み書きするために、他のゴルーチンは状態を持つゴルーチンにメッセージを送り、返信を受け取る。
構造体 |
type readOp struct {
key int
resp chan int
}
type writeOp struct {
key int
val int
resp chan bool
}
|
func main() {
|
|
前と同じように、操作を実行した回数を数える。 |
var readOps uint64
var writeOps uint64
|
チャネル |
reads := make(chan readOp)
writes := make(chan writeOp)
|
このゴルーチンが |
go func() {
var state = make(map[int]int)
for {
select {
case read := <-reads:
read.resp <- state[read.key]
case write := <-writes:
state[write.key] = write.val
write.resp <- true
}
}
}()
|
ここで100個のゴルーチンを開始し、チャネル |
for r := 0; r < 100; r++ {
go func() {
for {
read := readOp{
key: rand.Intn(5),
resp: make(chan int)}
reads <- read
<-read.resp
atomic.AddUint64(&readOps, 1)
time.Sleep(time.Millisecond)
}
}()
}
|
同様に、書き込みを行うゴルーチンも10個開始する。 |
for w := 0; w < 10; w++ {
go func() {
for {
write := writeOp{
key: rand.Intn(5),
val: rand.Intn(100),
resp: make(chan bool)}
writes <- write
<-write.resp
atomic.AddUint64(&writeOps, 1)
time.Sleep(time.Millisecond)
}
}()
}
|
1秒間待って、ゴルーチンに仕事をさせる。 |
time.Sleep(time.Second)
|
最後に操作回数を読み出し、表示する。 |
readOpsFinal := atomic.LoadUint64(&readOps)
fmt.Println("readOps:", readOpsFinal)
writeOpsFinal := atomic.LoadUint64(&writeOps)
fmt.Println("writeOps:", writeOpsFinal)
}
|
プログラムを実行するとゴルーチンを使った状態管理の例では約80000回の操作を実行できたことがわかる。 |
$ go run stateful-goroutines.go
readOps: 71708
writeOps: 7177
|
この例ではゴルーチンを使ったやり方はミューテックスを使う場合と比べて少し処理が多かった。 しかし、場合によってはこのやり方の方が便利なこともある。 例えば、他にもチャネルを使っている場合や、複数のミューテックスを使った結果プログラムを間違えそうな場合である。 プログラムの正しさがパッと見てわかるような、自然なやり方を採用するのがよい。 |
次の例:Sorting