Это хороший пример режима рабочих и контроллеров в Go, написанный @Jimt в ответ на вопрос « Есть ли какой-нибудь элегантный способ приостановить и возобновить любую другую горутину в 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.