如何在不阅读的情况下检查频道是否关闭?

新手上路,请多包涵

这是@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 许可协议

阅读 536
2 个回答

可以通过恢复引发的恐慌来尝试写入的通道以一种 hacky 的方式完成。但是你不能在不读取的情况下检查读取通道是否关闭。

要么你会

  • 最终从中读取“真实”值( v <- c
  • 读取“真实”值和“未关闭”指示器( v, ok <- c
  • 读取 零值 和“关闭”指示器( v, ok <- c )( 示例
  • 将永远阻塞在通道读取中( v <- c

只有最后一个在技术上不会从通道中读取,但这没什么用。

原文由 zzzz 发布,翻译遵循 CC BY-SA 4.0 许可协议

没有办法编写一个安全的应用程序,您需要在不与它交互的情况下知道通道是否打开。

做您想做的事情的最佳方式是使用两个通道——一个用于工作,一个用于指示改变状态的愿望(以及状态改变的完成,如果这很重要的话)。

渠道便宜。复杂的设计重载语义不是。

[还]

 <-time.After(1e9)

是一种非常混乱和不明显的写作方式

time.Sleep(time.Second)

保持简单,每个人(包括您)都能理解。

原文由 Dustin 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题