これは、@Jimt によって書かれた Go のワーカーとコントローラー モードの良い例です。
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 チームにとって役立つと思います。