为了账号安全,请及时绑定邮箱和手机立即绑定

在这个 golang 程序中如何避免死锁?

在这个 golang 程序中如何避免死锁?

Go
牛魔王的故事 2022-01-10 16:25:00
这是我的程序正在产生死锁,我该如何避免它以及处理这种情况的推荐模式是什么。问题是超时后如何检测到我的频道上没有阅读器?var wg sync.WaitGroupfunc main() {       wg.Add(1)    c := make(chan int)    go readFromChannel(c, time.After(time.Duration(2)*time.Second))    time.Sleep(time.Duration(5) * time.Second)    c <- 10    wg.Wait()}func readFromChannel(c chan int, ti <-chan time.Time) {    select {    case x := <-c:        fmt.Println("Read", x)    case <-ti:        fmt.Println("TIMED OUT")    }    wg.Done()}
查看完整描述

3 回答

?
繁星淼淼

TA贡献1775条经验 获得超11个赞

所以,让我们看看你的源代码中到底发生了什么。你有两个goroutine(不止两个,但我们将专注于显式的),main和readFromChannel.


让我们看看有什么readFromChannel作用:


if channel `c` is not empty before `ti` has expired, print its contents and return, after signalling its completion to wait group.

if `ti` has expired before `c` is not empty, print "TIMED OUT" and return, after signalling its completion to wait group.

现在主要:


adds to waitgroup 

make a channel `c`

start a goroutine `readFromChannel`

sleep for 5 seconds

send 10 to channel `c`

call wait for waitgroup

现在,让我们同时执行您的代码的执行流程(您的代码可能/可能不会每次都按此顺序执行,请记住这一点)


1) wg.Add(1)

2) c := make(chan int)

3) go readFromChannel(c, time.After(time.Duration(2)*time.Second))

#timer ti starts#

4) time.Sleep(time.Duration(5) * time.Second)

#MAIN Goroutine begins sleep

#timer ti expires#

5) case <-ti:

6) fmt.Println("TIMED OUT")

7) wg.Done()

# readFromChannel Goroutine returns #

#MAIN Goroutine exits sleep#

8) c<-10

9) ......#DEADLOCK#

现在你可以猜到为什么会出现死锁。在进行中,非缓冲通道将阻塞,直到通道的另一端发生某些事情,无论您是发送还是接收。所以c <- 10会阻塞,直到从 的另一端读取某些内容c,但是您为此拥有的 goroutine 已在 2 秒前从图片中删除。因此,c永远阻塞,并且由于main是最后一个 goroutine,你会得到一个死锁。


如何预防?使用频道时,请确保receive在频道的另一端始终有一个send.


在这种情况下使用缓冲通道可以作为一种快速修复,但可能会在更大的存储库中引发潜在的陷阱。例如,假设您c随后写入了更多数据并go readFromChannel(c, time.After(time.Duration(2)*time.Second))再次运行。你可能会看到:


Read D1

Read D2

或者


TIMED OUT

Read D1

完全基于机会。这可能不是您想要的行为。


以下是我解决死锁的方法:


func main() {

    wg.Add(1)

    c := make(chan int)

    go readFromChannel(c, time.After(time.Duration(2)*time.Second))

    time.Sleep(time.Duration(5) * time.Second)

    c <- 10

    wg.Wait()

}


func readFromChannel(c chan int, ti <-chan time.Time) {

        // the forloop will run forever

    loop: // **

    for {

        select {

            case x := <-c:

                    fmt.Println("Read", x)

                    break loop // breaks out of the for loop and the select **

            case <-ti:

                    fmt.Println("TIMED OUT")

            }

    }

    wg.Done()


查看完整回答
反对 回复 2022-01-10
?
慕桂英546537

TA贡献1848条经验 获得超10个赞

你有一个无缓冲的频道。根据文档

如果通道没有缓冲,发送方会阻塞,直到接收方收到该值。如果通道有缓冲区,发送方只会阻塞,直到值被复制到缓冲区

通过将通道更改为缓冲,我们可以避免死锁。

c := make(chan int, 10) // holds 10 ints

我还建议阅读https://golang.org/doc/effective_go.html#channels,里面有一些与频道相关的好东西。


查看完整回答
反对 回复 2022-01-10
?
守候你守候我

TA贡献1802条经验 获得超10个赞

这是一个较老的问题,但我自己正在深入学习渠道并在这里找到了这个。


我认为您只需要在完成发送后关闭频道吗?


代码:


func main() {

    wg.Add(1)

    c := make(chan int)

    go readFromChannel(c, time.After(time.Duration(2)*time.Second))

    time.Sleep(time.Duration(5) * time.Second)

    c <- 10

    close(c) // <- CLOSE IT HERE

    wg.Wait()

}


查看完整回答
反对 回复 2022-01-10
  • 3 回答
  • 0 关注
  • 221 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号