これは、@ Jimtによって書かれたGoのワーカーとコントローラーモードの良い例です。「golangで他のゴルーチンを一時停止および再開するためのエレガントな方法はありますか?」
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
// Possible worker states.
const (
Stopped = 0
Paused = 1
Running = 2
)
// Maximum number of workers.
const WorkerCount = 1000
func main() {
// Launch workers.
var wg sync.WaitGroup
wg.Add(WorkerCount + 1)
workers := make([]chan int, WorkerCount)
for i := range workers {
workers[i] = make(chan int)
go func(i int) {
worker(i, workers[i])
wg.Done()
}(i)
}
// Launch controller routine.
go func() {
controller(workers)
wg.Done()
}()
// Wait for all goroutines to finish.
wg.Wait()
}
func worker(id int, ws <-chan int) {
state := Paused // Begin in the paused state.
for {
select {
case state = <-ws:
switch state {
case Stopped:
fmt.Printf("Worker %d: Stopped\n", id)
return
case Running:
fmt.Printf("Worker %d: Running\n", id)
case Paused:
fmt.Printf("Worker %d: Paused\n", id)
}
default:
// We use runtime.Gosched() to prevent a deadlock in this case.
// It will not be needed of work is performed here which yields
// to the scheduler.
runtime.Gosched()
if state == Paused {
break
}
// Do actual work here.
}
}
}
// controller handles the current state of all workers. They can be
// instructed to be either running, paused or stopped entirely.
func controller(workers []chan int) {
// Start workers
for i := range workers {
workers[i] <- Running
}
// Pause workers.
<-time.After(1e9)
for i := range workers {
workers[i] <- Paused
}
// Unpause workers.
<-time.After(1e9)
for i := range workers {
workers[i] <- Running
}
// Shutdown workers.
<-time.After(1e9)
for i := range workers {
close(workers[i])
}
}
ただし、このコードにも問題があります。終了workers
時にワーカーチャネルを削除する場合worker()
、デッドロックが発生します。
もしそうならclose(workers[i])
、次にコントローラーがそれに書き込むとき、goは閉じたチャネルに書き込むことができないのでパニックを引き起こします。ミューテックスを使用して保護すると、チャネルから何も読み取っていないworkers[i] <- Running
ためにスタックし、worker
書き込みがブロックされ、ミューテックスによってデッドロックが発生します。回避策として、チャネルに大きなバッファを与えることもできますが、それだけでは十分ではありません。
したがって、これを解決する最善の方法はworker()
、終了時にチャネルを閉じることです。コントローラーがチャネルが閉じていることを検出すると、そのチャネルを飛び越えて何もしません。しかし、この状況でチャネルがすでに閉じられているかどうかを確認する方法が見つかりません。コントローラでチャネルを読み取ろうとすると、コントローラがブロックされる可能性があります。だから私は今のところ非常に混乱しています。
PS:発生したパニックを回復することは私が試みたものですが、それはパニックを発生させたゴルーチンを閉じます。この場合はコントローラーになるので無駄です。
それでも、Goチームがこの機能を次のバージョンのGoに実装することは有用だと思います。