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