这是@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()
退出时关闭通道,如果控制器发现通道关闭,它将跳过它并且什么也不做。但是在这种情况下我找不到如何检查频道是否已经关闭。如果我尝试读取控制器中的通道,控制器可能会被阻止。所以我现在很困惑。
PS:恢复引发的恐慌是我尝试过的,但它会关闭引起恐慌的goroutine。在这种情况下,它将成为控制器,因此没有用。
不过,我认为 Go 团队在下一版本的 Go 中实现此功能很有用。
原文由 Shane Hou 发布,翻译遵循 CC BY-SA 4.0 许可协议
可以通过恢复引发的恐慌来尝试写入的通道以一种 hacky 的方式完成。但是你不能在不读取的情况下检查读取通道是否关闭。
要么你会
v <- c
)v, ok <- c
)v, ok <- c
)( 示例)v <- c
)只有最后一个在技术上不会从通道中读取,但这没什么用。