이것은 @Jimt가 작성한 Go의 작업자 및 컨트롤러 모드의 좋은 예입니다 . " golang에서 다른 goroutine을 일시 중지하고 다시 시작할 수있는 우아한 방법이 있습니까? "
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()
종료 할 때 채널을 닫는 것입니다. 컨트롤러가 닫힌 채널을 발견하면 해당 채널을 건너 뛰고 아무것도하지 않습니다. 하지만이 상황에서 채널이 이미 닫혔는지 여부를 확인하는 방법을 찾을 수 없습니다. 컨트롤러에서 채널을 읽으려고하면 컨트롤러가 차단 될 수 있습니다. 그래서 지금은 매우 혼란 스럽습니다.
추신 : 제기 된 패닉을 복구하는 것은 내가 시도한 것이지만 패닉을 일으킨 고 루틴을 닫을 것입니다. 이 경우 컨트롤러가되므로 쓸모가 없습니다.
그래도 Go 팀이 다음 버전의 Go에서이 기능을 구현하는 것이 유용하다고 생각합니다.