V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
zyt19940914
V2EX  ›  Go 编程语言

关于 golang channel 的问题,希望懂的大佬能进来看看

  •  
  •   zyt19940914 · 2019-08-28 15:00:06 +08:00 · 3800 次点击
    这是一个创建于 1917 天前的主题,其中的信息可能已经有所发展或是发生改变。

    关于 channel 在 golang 中国的官网上写着这样一句话:

    使用信道需要考虑的一个重点是死锁。当 Go 协程给一个信道发送数据时,照理说会有其他 Go 协程来接收数据。如果没有的话,程序就会在运行时触发 panic,形成死锁。

    同理,当有 Go 协程等着从一个信道接收数据时,我们期望其他的 Go 协程会向该信道写入数据,要不然程序就会触发 panic。

    问题一: 如果是无缓冲的 channel,协程给一个信道发数据 但是没有其他协程接收数据,个人实测下来并没有出现死锁....

    问题二: 无缓冲的 channel 与有缓冲的 channel 发生死锁的场景区别,哪位能介绍下....?

    24 条回复    2019-08-29 09:35:55 +08:00
    choury
        1
    choury  
       2019-08-28 15:12:51 +08:00   ❤️ 1
    nybux
        2
    nybux  
       2019-08-28 15:14:31 +08:00
    package main

    func main() {
    ch := make(chan int)
    for {
    ch <- 1
    }
    }


    fatal error: all goroutines are asleep - deadlock!

    goroutine 1 [chan send]:
    main.main()
    a.go:7 +0x5c
    exit status 2
    zyt19940914
        3
    zyt19940914  
    OP
       2019-08-28 16:45:17 +08:00
    @nybux @choury 关于第二个问题 我自己实践发现,如果 channel 是有缓冲的,在 channel 被写满前,如果没有协程从通道内取,那不会产生死锁, 如果 channel 已经被写满了,这个时候再向里面写的时候就会报死锁。两位大佬认同我的总结吗......
    choury
        4
    choury  
       2019-08-28 17:13:03 +08:00
    @zyt19940914 是的,你可以把所有的 channel 都理解成有缓冲的,只是这个缓冲可能是 0,缓冲满了再写这个 goroutinues 就卡住,所有 goroutinues 都卡住就报死锁
    Gav1n
        5
    Gav1n  
       2019-08-28 17:37:00 +08:00
    楼主你好,问题 1 的代码能贴出来看看吗?
    Gav1n
        6
    Gav1n  
       2019-08-28 17:38:29 +08:00
    不好意思,我没看到协程,确实是这样的.等大佬来解释一下
    sujin190
        7
    sujin190  
       2019-08-28 17:57:15 +08:00
    可能你对死锁理解有点朴素了,这个的意思应该是,你往一个无缓冲的 channel 写入数据,这个写入过程必须等待有另外一个 goroutinue 来结收这个数据才能返回,如果这个时候再也没有任何一个另外 goroutinue 来结收数据了,那么这时候不是死锁了是啥?
    注意这个没有另外 goroutinue 来结收数据的意思,并不是当前没有另外一个 goroutinue 正在等待结收数据,而是当前和以后都不会有另外一个 goroutinue 来结收这个数据了,这种情况下应该是除了写入数据这个 goroutinue 以外所有 goroutinue 都已经执行结束,整个程序再也不会有另可运行的 goroutinue 来读取这个数据。

    对于有缓冲的 channel,则是写入数据当时如果没有另外 goroutinue 来读取这个数据,则放入缓冲中,自己可以返回来读取缓冲中的数据,所以这个并不会造成死锁。
    1608637229
        8
    1608637229  
       2019-08-28 18:02:11 +08:00
    借一下楼问一个简单的问题
    https://tour.go-zh.org/moretypes/11
    这里的第 10 行的 s = s[:0]
    s 被赋值为了 0 个为什么后面还能继续 s = s[:4]
    有点不能理解,按照我的理解难道不是 s 已经被赋值为空了吗
    sujin190
        9
    sujin190  
       2019-08-28 18:03:21 +08:00
    更简洁一点的说法应该是,我当前数据需要另外一个 goroutinue 来读取,而另一个可以读取这个数据 goroutinue 需要我来创建,所以这个和传统的死锁并没有什么区别。

    等待读取数据也是同理的。
    zichen
        10
    zichen  
       2019-08-28 18:03:56 +08:00
    这个不叫死锁应该叫阻塞吧……
    1608637229
        11
    1608637229  
       2019-08-28 18:08:09 +08:00
    @1608637229 难道是和 c 语言一样的指针吗,赋值 0 的时候依然还是数组的头地址没变。我不知道有没有 go 大佬解答一下
    sujin190
        12
    sujin190  
       2019-08-28 18:39:25 +08:00
    @1608637229 #8 这个和 c 语言完全不一样吧,不是元素访问,似乎 C 语言也没有这个操作的吧
    这个是对数组进行切片操作,空数组切片操作选择前四个不还是空数组么,内部应该有 min(4, len(s))之类的操作
    zyt19940914
        13
    zyt19940914  
    OP
       2019-08-28 18:39:56 +08:00
    sujin190
        14
    sujin190  
       2019-08-28 18:43:11 +08:00
    @zichen #10 阻塞的意思是现在将来一段时间等待其他事件,等待时间内不能做其他事情,但是等待肯定是可以有结果的,但是如果永远不会有结果返回,这个就叫死锁了啊
    zyt19940914
        15
    zyt19940914  
    OP
       2019-08-28 18:44:21 +08:00
    @zichen 运行的时候显示 fatal error: all goroutines are asleep - deadlock! 可能因为发生在主线程中.....? 所以报死锁 如果不是主线程,那是阻塞
    inhzus
        16
    inhzus  
       2019-08-28 18:48:18 +08:00
    @1608637229 #8 如果看过 stl 源码的话,golang slice 比较类似于 c++ vector,可以对比一下就理解了
    lilydjwg
        17
    lilydjwg  
       2019-08-28 18:49:42 +08:00
    @zichen #10 大概是因为 channel 的实现里有个锁,然后没有接收者的时候它就死了?

    Go 的 channel 不能在发送时知道有没有接收方吗(不管它在不在接收)? Rust 的 channel 如果接收方不在了是会返回错误的,Go 应该也能做到的吧?
    1608637229
        18
    1608637229  
       2019-08-28 18:55:18 +08:00
    @sujin190 看来下就是头指针,s = s[:0]头指针没有改变。改变的是 len (变为 0 )。因此后面只可以再赋值的时候 s = s[:4]只是把 len 改成了 4.
    lilydjwg
        19
    lilydjwg  
       2019-08-28 18:57:22 +08:00
    @1608637229 #11 不为什么。不需要理解。这是 Go 语言规范所规定的:

    > The capacity is a measure of that extent: it is the sum of the length of the slice and the length of the array beyond the slice; a slice of length up to that capacity can be created by slicing a new one from the original slice.

    https://golang.google.cn/ref/spec#Slice_types

    @sujin190 #12 你点一下「 run 」就会发现这个和 Python 的切片不一样的。
    zarte
        20
    zarte  
       2019-08-28 19:01:12 +08:00
    问题一,这个有可能是你创建通道未初始化,然后传与取都是正常执行不过没数据。
    问题二:
    jobs := make(chan []string, len(newlist))
    go 协程一
    for{
    jobs <- addr
    }
    go 协程二
    func threadworker(id int, jobs <-chan []string) {
    for j := range jobs {
    liulaomo
        21
    liulaomo  
       2019-08-28 20:47:46 +08:00
    @zyt19940914

    > 使用信道需要考虑的一个重点是死锁。当 Go 协程给一个信道发送数据时,照理说会有其他 Go 协程来接收数据。如果没有的话,程序就会在运行时触发 panic,形成死锁。
    > 同理,当有 Go 协程等着从一个信道接收数据时,我们期望其他的 Go 协程会向该信道写入数据,要不然程序就会触发 panic。

    这个需要需要上下文情景理解。 不过无论如何,这么描述都是欠妥的。这两句话想强调的是当所有用户协程都同时阻塞时,程序将崩溃退出(非 panic )。但这只是官方编译器的实现,白皮书对此并未做要求。

    @1608637229
    取子切片时,第二个下标可以大于基础切片的长度,但是不能大于基础切片的容量。具体见 @lilydjwg 列出的白皮书中的一段。
    janxin
        22
    janxin  
       2019-08-29 08:08:09 +08:00
    Go 的 runtime 是在调度过程中发现所有 goroutine 都处于等待状态时就才会发出 all goroutines are asleep - deadlock panic 的。这里比较容易迷惑人的是:不是出现了 panic 才有死锁,在实际应用中不出现 panic 的时候也有可能有死锁。
    janxin
        23
    janxin  
       2019-08-29 08:21:42 +08:00
    @lilydjwg 这个是调度器检查的,当所有 goroutine 都阻塞住了才 panic。
    poplar50
        24
    poplar50  
       2019-08-29 09:35:55 +08:00 via Android
    重点不是缓不缓冲,死锁是资源占用逻辑问题,如果一个协程需要从 channel 接收数据,主程发现除了该协程其他协程已经执行完毕了,那么就那个协程就永远接收不到数据,一直 block。
    同理 一个协程需要发送数据,但 channel 已满,这是它是 block 的状态,然后如果其他协程都已经执行完毕了,channel 仍然是慢的,此时这个协程如果不控制也会一直 block 下去,这个时候,也应该报死锁。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5351 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 05:45 · PVG 13:45 · LAX 21:45 · JFK 00:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.