请查看以下代码段。
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.
答案 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)和其他人都有一些特殊的宝石。
客户端 - 服务器策略很简单:描述您的Go例程 网络作为一组通信服务器及其客户;确保 网络图中没有循环=&gt;死锁是 消除。
I / o-par是一种形成Go-routines的环和环的方法 结构内不会出现僵局;这是一个 允许循环 但在a中表现的特殊情况 一般无死锁的方式。
所以,我的策略是首先减少缓冲区大小,考虑发生了什么,修复死锁。然后,根据基准测试重新引入缓冲区以提高性能。死锁是由通信图中的循环引起的。打破循环。
答案 2 :(得分:1)
由于您在rand == 3
和err3()
中有意使用了err4()
,因此可以有2个解决方案:
将chErr
频道的缓冲区大小增加到至少2,因为在您的程序中使用n = 3
可能会导致2个goroutines在频道上发送值。
最好在所有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博文中了解有关非阻止发送的更多信息。