我怎样才能避免死锁

时间:2015-01-21 09:19:31

标签: go

请查看以下代码段。

package main

import (
    "errors"
    "fmt"
    "math/rand"
    "runtime"
    "sync"
    "time"
)

func random(min, max int) int {
    rand.Seed(time.Now().Unix())
    return rand.Intn(max-min) + min
}

func err1(rand int, chErr chan error, wg *sync.WaitGroup) {
    if rand == 1 {
        chErr <- errors.New("Error 1")
    }

    wg.Done()

}

func err2(rand int, chErr chan error, wg *sync.WaitGroup) {
    if rand == 2 {
        chErr <- errors.New("Error 2")
    }
    wg.Done()
}

func err3(rand int, chErr chan error, wg *sync.WaitGroup) {
    if rand == 3 {
        chErr <- errors.New("Error 3")
    }
    wg.Done()
}

func err4(rand int, chErr chan error, wg *sync.WaitGroup) {
    if rand == 3 {
        chErr <- errors.New("Error 4")
    }
    wg.Done()
}

func err5(rand int, chErr chan error, wg *sync.WaitGroup) {
    if rand == 4 {
        chErr <- errors.New("Error 5")
    }
    wg.Done()
}

func main() {

    runtime.GOMAXPROCS(runtime.NumCPU())

    chErr := make(chan error, 1)
    wg := new(sync.WaitGroup)

    //n := random(1, 8)
    n := 3
    fmt.Println(n)

    wg.Add(5)
    go err1(n, chErr, wg)
    go err2(n, chErr, wg)
    go err3(n, chErr, wg)
    go err4(n, chErr, wg)
    go err5(n, chErr, wg)

    fmt.Println("Wait")
    wg.Wait()
    select {
    case err := <-chErr:
        fmt.Println(err)
        close(chErr)
    default:
        fmt.Println("NO error, job done")
    }
}

我怎样才能避免死锁?我可以指定缓冲区长度2,但也许它有更优雅的方法来解决问题。

我有意识地在函数err3和err4上做了rand == 3.

3 个答案:

答案 0 :(得分:3)

您的程序已陷入僵局,因为您的频道已满。

您的频道尺寸为1。然后你调用wg.Wait() ..等待调用5个函数。现在,一旦您到达err3 .. rand == 3,就会在您的频道上传递错误。

此时,您的频道已满,并且您只勾选了3个等待组项目。

使用值3调用

err4,它也想在您的频道上输入错误。此时,它会阻止 - 因为您的频道已满,并且没有任何内容从中弹出。

所以你的主要goroutine会阻止因为你的waitgroup永远不会完成。

修复确实是为了让您的频道缓冲区更大。这样,当错误试图被放置在频道上时 - 它不会被阻止,并且你的waitgroup有机会勾选所有项目。

答案 1 :(得分:3)

通常,不要陷入认为更大的缓冲区修复死锁的陷阱。这种方法可能在某些特定情况下有效,但通常情况并非如此。

最好通过了解goroutine如何相互依赖来解决死锁问题。从本质上讲,您必须消除存在相互依赖的通信循环。非阻塞发送的想法(参见@ izca的回答)是一个有用的技巧,但不是唯一的。

关于如何避免死锁/活锁,有相当多的知识。其中很大一部分来自奥卡姆在80年代和90年代流行的日子。杰里米·马丁(Design Strategy for Deadlock-Free Concurrent Systems),彼得·韦尔奇(Higher Level Paradigms)和其他人都有一些特殊的宝石。

  1. 客户端 - 服务器策略很简单:描述您的Go例程 网络作为一组通信服务器及其客户;确保 网络图中没有循环=&gt;死锁是 消除。

  2. I / o-par是一种形成Go-routines的环和环的方法 结构内不会出现僵局;这是一个 允许循环 但在a中表现的特殊情况 一般无死锁的方式。

  3. 所以,我的策略是首先减少缓冲区大小,考虑发生了什么,修复死锁。然后,根据基准测试重新引入缓冲区以提高性能。死锁是由通信图中的循环引起的。打破循环。

    Related answer

答案 2 :(得分:1)

由于您在rand == 3err3()中有意使用了err4(),因此可以有2个解决方案:

1。增加通道的缓冲区大小

chErr频道的缓冲区大小增加到至少2,因为在您的程序中使用n = 3可能会导致2个goroutines在频道上发送值。

2。使用非阻塞发送

最好在所有errX()个功能中使用非阻止频道发送(但至少在err3()err4(),因为它们发送的条件相同){{1} }:

select

这会尝试在频道上发送select { case chErr <- errors.New("Error 3"): default: } ,但如果它没有准备好(如果它已满,因为另一个goroutine已经发送了一个值),error案例将是选择哪个什么都不做。

Go Playground上试用。

注意:这将失去&#34;其中一个错误,因为通道只能容纳一个错误,但无论如何你只能从中读取(接收)一个值。

您可以在Go Concurrency Patterns: Timing out, moving on博文中了解有关非阻止发送的更多信息。