“通道写完后,必须关闭通道,否则range遍历会出现死锁”这句话对吗?

通道写完后,必须关闭通道,否则range遍历会出现死锁,请问这句话对吗?

那么如果我有一个场景,一个goroutine往通道写数据,一个或多个goroutine从通道读数据,这种情况是没有关闭通道的,那应该怎么写?


补充:

我提的问题有点没说清楚,这个往通道写是个死循环的不断写的过程,读也是不断的读,所以你怎么关闭通道?目前我是开一个goroutine不断写,开5个goroutine用range读,尚没有问题,但是看书有这么一句所以问问。

clipboard.png

这个通道是不断写数据的,所以不存在“通道写完后”,所以也无法关闭,那么另外的goroutine就只管range读取数据就好了可以吗?,问题改成这个,理解对吗,目前程序运行没有报错,只是请教一下这样写是否规范。

阅读 7.9k
4 个回答
通道写完后,必须关闭通道,否则range遍历会出现死锁,请问这句话对吗?

没错, 关闭 channel 会导致 for range 退出循环.

但, 用简单关闭 channel 的方式控制读写 routine 是危险的, 因为写入已经关闭的 channel 会直接崩溃.
所以你得给 channel 加个状态值, 防止这种情况发生. 当然了, 你也可以不用 channel, 自己用锁实现.

下面举两个例子, 分别是利用 channel 和不用 channel 实现的 生产/消费 模型.
注意: 例子只提供思路, 并不完善.

用 channel

package main

import (
    "sync"
)

type Store struct {
    sync.Mutex // 写\关闭 channel 时用
    ch         chan int
}

func NewStore() *Store {
    return &Store{ch: make(chan int, 128)}
}

func (store *Store) Read() int {
    return <-store.ch
}

func (store *Store) Write(value int) {
    store.Lock()
    defer store.Unlock()
    store.ch <- value
}

func (store *Store) Close() {
    store.Lock()
    defer store.Unlock()
    close(store.ch)
}

不用 channel

package main

import (
    "sync"
)

type Store2 struct {
    sync.Mutex
    link   []int
    closed bool
}

func (store *Store2) Read() int {
    store.Lock()
    defer store.Unlock()
    if len(store.link) == 0 {
        return -1
    }
    value := store.link[0]
    store.link = store.link[1:]
    return value
}

func (store *Store2) Write(value int) {
    store.Lock()
    defer store.Unlock()
    if !store.closed {
        store.link = append(store.link, value)
    }
}

func (store *Store2) Close() {
    store.Lock()
    defer store.Unlock()
    store.closed = true
}

如果你有明确的写入数的,那可以写完就close,然后用for range读。

如果你写入的数量是不确定的,那就无法close。没close的不能用for range,只能是for{}死循环读(类似消息列队,反正你写入多少我就读多少)。

当然了,就算是死循环,也最好有结束条件的。比如使用select+超时的控制

首先,建议多读写入门资料、系统的学习,看过很多代码,再写过很多验证/Demo/试错代码后,后就不会有这些疑问。

问题

  1. 通道写完后,必须关闭通道,否则range遍历会出现死锁,请问这句话对吗?
    1)通道用完后必须关闭:✔️,因为,在长时间运行的程序中,不关闭通道是资源泄露。
    2)否则range遍历会出现死锁:不一定是死锁,只是你运行range的那个goroutine不退出而已。

    比如这就死锁:

    func main() {
        ch := make(chan int)
        go func() {
            for i := 0; i < 100; i++ {
                ch <- i
            }
        }()
    
        go func() {
            for x := range ch {
                fmt.Println(x)
            }
        }()
    }
  2. 那么如果我有一个场景,一个goroutine往通道写数据,一个或多个goroutine从通道读数据,这种情况是没有关闭通道的,那应该怎么写?
    写完时关闭。例子:

    ch := make(chan int)
    go func() {
        defer close(ch)
        for i := 0; i < 100; i++ {
            ch <- i
        }
    }()
    
    for x := range ch {
        fmt.Println(x)
    }

最后

使用通道关闭退出goroutine是一种正常、通用的实践,并且安全。前提,是你真正的掌握了channel,这很简单,但需要多实践,可看一下这篇文章:https://segmentfault.com/a/11...

新手上路,请多包涵

for range对不知长度的通道好像不会报错:

    ch := make(chan int)
    go func(varch chan<- int) {
        //defer close(ch)
        //for i := 0; i < 100; i++ {
        //    ch <- i
        //}
        for i:=1;;i++{
            varch <- i
            time.Sleep(time.Second)
        }
    }(ch)

    for x := range ch {
        fmt.Println(x)
    }
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏