go - 如何避免这个 golang 程序中的死锁?

标签 go goroutine

这是我的程序产生死锁,我该如何避免它以及处理这种情况的推荐模式是什么。

问题是超时后如何检测我的 channel 上没有阅读器?

var wg sync.WaitGroup

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) {
    select {
    case x := <-c:
        fmt.Println("Read", x)
    case <-ti:
        fmt.Println("TIMED OUT")
    }
    wg.Done()
}

最佳答案

那么,让我们看看源代码中到底发生了什么。你有两个 goroutines(有两个以上,但我们将关注显式的),mainreadFromChannel .

让我们看看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#

现在您可以猜出为什么会出现死锁。在 go 中,非缓冲 channel 将阻塞,直到 channel 的另一端发生某些事情,无论您是发送还是接收。所以c <- 10将阻塞直到有东西从 c 的另一端读取,但是你为此使用的 goroutine 已经在 2 秒前从图片中删除了。因此,c永远阻塞,因为main是最后一个 goroutine,你会遇到死锁。

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

在这种情况下使用缓冲 channel 可以作为一种快速修复,但可能会加剧大型存储库中的潜在问题。例如,假设您将更多数据写入 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()
} 

** see this answer for details

关于go - 如何避免这个 golang 程序中的死锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37210109/

相关文章:

go - 从 PEM 文件加载(openssl 生成的)DSA 私钥

go - channel是否发送抢占点用于goroutine调度?

go - 如何停止正在监听 RethinkDB 变更源的 goroutine?

go - 为什么这段关于golang goruntine运行顺序的代码首先是 "2"

mongodb - 如何使用 Go 从 MongoDB 中删除单个文档

pointers - 接口(interface)不能调用self方法

database - 如何使用 gorm 写入特定的数据库模式?

Go 二进制编码变体与固定 slice 长度

Goroutines 不能并行工作

multithreading - 为什么这段代码没有达到竞争条件?