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

Emb*_*rja 6 go goroutine

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

问题是在超时后如何检测到我的频道上没有读卡器?

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()
}
Run Code Online (Sandbox Code Playgroud)

Anf*_*nee 8

所以,让我们看一下你的源代码中究竟发生了什么.你有两个 goroutines(有两个以上,但我们将专注于显式的),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.
Run Code Online (Sandbox Code Playgroud)

现在主要:

adds to waitgroup 
make a channel `c`
start a goroutine `readFromChannel`
sleep for 5 seconds
send 10 to channel `c`
call wait for waitgroup
Run Code Online (Sandbox Code Playgroud)

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

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#
Run Code Online (Sandbox Code Playgroud)

现在你可以猜到你为什么会陷入僵局.在go中,非缓冲通道将阻塞,直到通道另一端发生某些事情,无论您是发送还是接收.所以c <- 10会阻止直到从另一端读取某些东西c,但是你的那个goroutine已经在2秒前退出了画面.因此,c永远阻止,并且从main最后一个goroutine开始,你得到一个死锁.

怎么预防呢?使用频道时,请确保receive每个频道的另一端始终存在频道send.您也可以使用缓冲通道,但在上面的代码中,它不是"正确"的解决方案.

这是我对死锁的修复:

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()
} 
Run Code Online (Sandbox Code Playgroud)

**有关详细信息,请参阅此答案


wil*_*.09 1

您有一个无缓冲的通道。根据文档:

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

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

c := make(chan int, 10) // holds 10 ints
Run Code Online (Sandbox Code Playgroud)

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