这是@Jimt在Go中编写的工作程序和控制器模式的一个很好的示例, ”
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()发生死锁。
workers
worker()
如果您使用close(workers[i]),下次控制器写入该命令将导致恐慌,因为go无法写入一个已关闭的通道。如果您使用某些互斥锁来保护它,则workers[i] <- Running由于worker它不会从通道读取任何内容,并且将阻止写入,因此互斥锁将导致死锁。您还可以为频道提供更大的缓冲,以解决此问题,但这还不够。
close(workers[i])
workers[i] <- Running
worker
因此,我认为解决此问题的最佳方法是worker()退出时关闭通道,如果控制器发现通道已关闭,它将跳过该通道并且不执行任何操作。但是在这种情况下,我找不到如何检查通道是否已关闭的信息。如果我尝试读取控制器中的通道,则控制器可能被阻止。所以我现在很困惑。
PS:恢复引发恐慌是我尝试过的方法,但是它将关闭引起恐慌的goroutine。在这种情况下它将是控制器,所以没有用。
不过,我认为Go团队在下一个Go版本中实现此功能还是有用的。
通过变通的方式,可以通过恢复引发的恐慌来尝试写入的通道。但是,如果不从中读取一个读取通道,则无法检查它是否已关闭。
要么你会
v <- c
v, ok <- c
从技术上讲,只有最后一个不会从该频道读取,但这没什么用。