我怎样才能避免死锁
How can I avoid deadlock
看下面的代码片段。
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。
既然你写的是故意在err3()
和err4()
中使用rand == 3
,那么可以有2个解决方案:
1。增加通道的缓冲区大小
将 chErr
通道的缓冲区大小增加到至少 2,因为在您的程序中使用 n = 3
可能会导致 2 个 goroutine 在通道上发送一个值。
2。使用非阻塞发送
最好在所有 errX()
函数中使用非阻塞通道发送(但至少在 err3()
和 err4()
中,因为它们在相同条件下发送)和 select
:
select {
case chErr <- errors.New("Error 3"):
default:
}
这将尝试在通道上发送一个 error
但如果它没有准备好(如果它已满,因为另一个 goroutine 已经发送了一个值),将选择 default
情况什么都不做。
在 Go Playground 上试用。
注意:这将 "lose" 错误之一,因为通道只能容纳一个错误,但无论如何您只能从中读取(接收)一个值。
您可以在 Go Concurrency Patterns: Timing out, moving on 博客文章中阅读有关非阻塞发送的更多信息。
您的程序已死锁,因为您的频道已满。
您的频道大小为一。然后你调用 wg.Wait()
.. 等待调用 5 个函数。现在,一旦您到达 err3
.. rand == 3
,因此您的频道会传递一个错误。
此时,您的频道已满,您只勾选了等待组中的 3 个项目。
err4
以值 3 .. 调用,这也想在您的频道上放一个错误。此时,它会阻塞 - 因为您的频道已满,没有任何内容从中弹出。
所以你的主 goroutine 会阻塞,因为你的等待组永远不会完成。
解决方法确实是让您的频道缓冲区更大。这样,当错误试图放置在通道上时 - 它不会阻塞,并且您的等待组有机会勾选其所有项目。
一般来说,不要陷入认为更大的缓冲区可以解决死锁的陷阱。这种方法可能适用于某些特定情况,但通常并非如此。
最好通过了解 goroutines 如何相互依赖来解决死锁。本质上,您必须消除相互依赖的通信循环。非阻塞发送想法(参见@izca 的回答)是一个有用的技巧,但不是唯一的。
有大量关于如何避免 deadlock/livelock 的知识。其中大部分来自奥卡姆在 80 年代和 90 年代流行的日子。 Jeremy Martin(无死锁设计策略
Concurrent Systems)、Peter Welch (Higher Level Paradigms) 和其他人。
客户端-服务器策略很简单:描述你的 Go-routine
网络作为一组通信服务器及其客户端;确保
网络图中没有循环 => 死锁是
淘汰了。
I/o-par 是一种形成 Go 例程的环和圆环的方法,这样
结构内不会出现死锁;这是一个
允许循环但表现在
一般无死锁方式。
所以,我的策略是先减少 缓冲区大小,想想发生了什么,修复死锁。然后,根据基准重新引入缓冲区以提高性能。死锁是由通信图中的循环引起的。打破循环。
Related answer
看下面的代码片段。
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。
既然你写的是故意在err3()
和err4()
中使用rand == 3
,那么可以有2个解决方案:
1。增加通道的缓冲区大小
将 chErr
通道的缓冲区大小增加到至少 2,因为在您的程序中使用 n = 3
可能会导致 2 个 goroutine 在通道上发送一个值。
2。使用非阻塞发送
最好在所有 errX()
函数中使用非阻塞通道发送(但至少在 err3()
和 err4()
中,因为它们在相同条件下发送)和 select
:
select {
case chErr <- errors.New("Error 3"):
default:
}
这将尝试在通道上发送一个 error
但如果它没有准备好(如果它已满,因为另一个 goroutine 已经发送了一个值),将选择 default
情况什么都不做。
在 Go Playground 上试用。
注意:这将 "lose" 错误之一,因为通道只能容纳一个错误,但无论如何您只能从中读取(接收)一个值。
您可以在 Go Concurrency Patterns: Timing out, moving on 博客文章中阅读有关非阻塞发送的更多信息。
您的程序已死锁,因为您的频道已满。
您的频道大小为一。然后你调用 wg.Wait()
.. 等待调用 5 个函数。现在,一旦您到达 err3
.. rand == 3
,因此您的频道会传递一个错误。
此时,您的频道已满,您只勾选了等待组中的 3 个项目。
err4
以值 3 .. 调用,这也想在您的频道上放一个错误。此时,它会阻塞 - 因为您的频道已满,没有任何内容从中弹出。
所以你的主 goroutine 会阻塞,因为你的等待组永远不会完成。
解决方法确实是让您的频道缓冲区更大。这样,当错误试图放置在通道上时 - 它不会阻塞,并且您的等待组有机会勾选其所有项目。
一般来说,不要陷入认为更大的缓冲区可以解决死锁的陷阱。这种方法可能适用于某些特定情况,但通常并非如此。
最好通过了解 goroutines 如何相互依赖来解决死锁。本质上,您必须消除相互依赖的通信循环。非阻塞发送想法(参见@izca 的回答)是一个有用的技巧,但不是唯一的。
有大量关于如何避免 deadlock/livelock 的知识。其中大部分来自奥卡姆在 80 年代和 90 年代流行的日子。 Jeremy Martin(无死锁设计策略 Concurrent Systems)、Peter Welch (Higher Level Paradigms) 和其他人。
客户端-服务器策略很简单:描述你的 Go-routine 网络作为一组通信服务器及其客户端;确保 网络图中没有循环 => 死锁是 淘汰了。
I/o-par 是一种形成 Go 例程的环和圆环的方法,这样 结构内不会出现死锁;这是一个 允许循环但表现在 一般无死锁方式。
所以,我的策略是先减少 缓冲区大小,想想发生了什么,修复死锁。然后,根据基准重新引入缓冲区以提高性能。死锁是由通信图中的循环引起的。打破循环。
Related answer