こんにちは。KOUKIです。
とあるWeb系企業で、Go言語エンジニアをしています。
皆さんは、syncパッケージのSignalメソッドを使ってますでしょうか。
僕はこの存在自体知らなかったですw
ただ、最近、Udemyのコースでこのメソッドの存在を知ったので、紹介したいと思います。
これを使うと「Gorutineの処理をとある状態になるまで待つ」などの処理が書けるようになるため、結構プログラミングの幅が広がると思います。
<目次>
サンプルコード
まず、Signalメソッド実装前のサンプルコードを以下に記載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package main import ( "fmt" "sync" "time" ) // シェアする変数 var sharedRsc = make(map[string]interface{}) func main() { var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() for len(sharedRsc) == 0 { fmt.Println("Wait...") } fmt.Println("Reading...") fmt.Println(sharedRsc["rsc1"]) }() // writes changes to sharedRsc time.Sleep(1 * time.Microsecond) fmt.Println("Writing...") sharedRsc["rsc1"] = "foo" wg.Wait() } |
「sharedRsc」変数は、goroutine間で読み書きする共通変数です。
処理の流れとしては、main goroutineがsharedRsc変数に「foo」を書き込むまで、Wait(プログラムの停止)をさせたいと考えています。
しかし、現実は以下のようになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ go run main.go Wait... Writing... Wait... Wait... Wait... Wait... Wait... Wait... Wait... Wait... Reading... foo |
「foo」が表示されているのでこのプログラムは一見問題なさそうに見えます。しかし、Waitがやたら出力され無駄な処理が実行されていることがわかります。
sync.Condを使ってみよう
こんな状況の時、誰もがこう感じると思います。
「fooが書き込まれたらシグナル(Signal)送って、プログラムを実行できないかな」
できます。
sync.Condを使えば、できます(大切なことなので、2回書きました)。
排他制御準備
sync.Condを使うために、まずは、NewCondします。
1 2 3 4 5 6 |
func main() { var wg sync.WaitGroup var mu sync.Mutex c := sync.NewCond(&mu) ... } |
NewCondするために、最初にMutexを宣言してそれを引数に渡しています。これで排他制御の準備が完了です。
Mutexについては、以下の記事をどうぞ。
書き込み/読み込み制御
次に、書き込みと読み込みを制御します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
func main() { ... go func() { defer wg.Done() c.L.Lock() for len(sharedRsc) == 0 { fmt.Println("Wait...") } fmt.Println("Reading...") fmt.Println(sharedRsc["rsc1"]) c.L.Unlock() }() // writes changes to sharedRsc time.Sleep(1 * time.Microsecond) fmt.Println("Writing...") c.L.Lock() sharedRsc["rsc1"] = "foo" c.L.Unlock() wg.Wait() } |
この辺りの使い方は、Mutexと大体同じです。書き込み、読み込みが発生する処理の前で、Lock/UnLockをするだけです。sync.Condは内部に「Locker」を持っていて、それがLock/UnLockメソッドを実装してます。
この状態でプログラムを送ると以下のように「Wait….」がずっと表示される状態になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
go run main.go Wait... Wait... Wait... Wait... Wait... Wait... Wait... Wait... Wait... Wait... Wait... Wait... |
Signalを待とう
Waitメソッドを使って、Signalが送られてくるまで待つ処理を追加します。
1 2 3 4 5 6 7 8 9 |
func main() { ... go func() { ... for len(sharedRsc) == 0 { fmt.Println("Wait...") c.Wait() } } |
プログラムを実行するとエラーが発生します。
1 2 3 4 |
$ go run 21-cond/main.go Wait... Writing... fatal error: all goroutines are asleep - deadlock! |
いつまでもSignalが送られてこないので、Waitが怒ったようです。
Signalを送る
sharedRsc変数に「foo」が書き込まれたらSignalを送りたいので、以下のように書き込み処理の直後にSignalメソッドを追加します。
1 2 3 4 5 6 7 |
// writes changes to sharedRsc time.Sleep(1 * time.Microsecond) fmt.Println("Writing...") c.L.Lock() sharedRsc["rsc1"] = "foo" c.Signal() c.L.Unlock() |
プログラムを実行してみましょう。
1 2 3 4 5 |
$ go run 21-cond/main.go Wait... Writing... Reading... foo |
OKですね。これで完了です。
おわりに
Signalメソッドを使えば、結構簡単にgoroutineを制御することができそうですね。
手軽だからこそ、バグも発生しそうではありますが^^;
それでは、また!
Go言語まとめ

プログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
package main import ( "fmt" "sync" "time" ) // シェアする変数 var sharedRsc = make(map[string]interface{}) func main() { var wg sync.WaitGroup var mu sync.Mutex c := sync.NewCond(&mu) wg.Add(1) go func() { defer wg.Done() c.L.Lock() for len(sharedRsc) == 0 { fmt.Println("Wait...") c.Wait() } fmt.Println("Reading...") fmt.Println(sharedRsc["rsc1"]) c.L.Unlock() }() // writes changes to sharedRsc time.Sleep(1 * time.Microsecond) fmt.Println("Writing...") c.L.Lock() sharedRsc["rsc1"] = "foo" c.Signal() c.L.Unlock() wg.Wait() } |
コメントを残す
コメントを投稿するにはログインしてください。