To jest dobry przykład trybu pracowników i kontrolera w Go napisany przez @Jimt, w odpowiedzi na pytanie „ Czy istnieje jakiś elegancki sposób na wstrzymanie i wznowienie innych goroutine w 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])
}
}
Ale ten kod ma również problem: jeśli chcesz usunąć kanał roboczy w workers
momencie worker()
wyjścia, nastąpi martwa blokada.
Jeśli ty close(workers[i])
, następnym razem, gdy kontroler zapisze do niego, wywoła panikę, ponieważ go nie może pisać do zamkniętego kanału. Jeśli użyjesz jakiegoś mutexa do ochrony, to utknie, workers[i] <- Running
ponieważ worker
nie czyta niczego z kanału i zapis zostanie zablokowany, a mutex spowoduje martwą blokadę. Możesz również dać większy bufor do kanału jako obejście, ale to nie wystarczy.
Myślę więc, że najlepszym sposobem rozwiązania tego problemu jest worker()
zamknięcie kanału przy wyjściu, jeśli sterownik znajdzie kanał zamknięty, przeskoczy nad nim i nic nie zrobi. Ale nie mogę znaleźć, jak sprawdzić, czy kanał jest już zamknięty, czy nie w tej sytuacji. Jeśli spróbuję odczytać kanał w kontrolerze, kontroler może być zablokowany. Więc na razie jestem bardzo zdezorientowany.
PS: Próbowałem odzyskać podniesioną panikę, ale zamknie to gorutynę, która wywołała panikę. W tym przypadku będzie to kontroler, więc nie ma sensu.
Mimo to myślę, że dla zespołu Go przydatne będzie zaimplementowanie tej funkcji w następnej wersji Go.