这是我的程序,它会产生死锁,如何避免死锁,以及建议使用哪种模式来处理这种情况。
问题是超时后如何检测我的频道上没有阅读器?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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(有两个以上,但我们将重点放在显式的)上,
让我们看看
1 2 | 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. |
现在主要:
1 2 3 4 5 6 | 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 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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# |
现在您可以猜测为什么会陷入僵局。进行中,非缓冲通道将阻塞,直到该通道的另一端发生任何事情,无论您是发送还是接收。因此,
怎么预防呢?使用通道时,请确保每个
这是我的僵局解决方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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() } |
**有关详细信息,请参见此答案
-
这是我一段时间以来阅读的最有用的答案之一。 只是要确保我遵循:"修复"之所以有效,是因为即使在超时后,它也可使接收器通道继续运行。 因此,只有从
c 读入内容时,wg.Done() (并终止main go例程)才会发生,对吗? - 没错,但是要清理一点,它可以使goroutine保持运行状态。
您有一个无缓冲的频道。根据文档:
If the channel is unbuffered, the sender blocks until the receiver has
received the value. If the channel has a buffer, the sender blocks
only until the value has been copied to the buffer
通过将通道更改为缓冲通道,我们可以避免死锁。
1 | c := make(chan int, 10) // holds 10 ints |
我还建议阅读https://golang.org/doc/effective_go.html#channels,那里有一些与频道有关的好东西。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | func main() { wg.Add(1) c := make(chan int) go func() { c <- 10 }() go readFromChannel(c, time.After(time.Duration(2)*time.Second)) time.Sleep(time.Duration(5) * time.Second) wg.Wait() }//This will read without deadlock 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) go func() { c <- 10 }() wg.Wait() }//Time out without deadlock |
- 这可以解释您所做的事情/与原始内容有何不同
这是一个比较老的问题,但是我本人正在深入学习渠道,并在这里找到了。
我认为您只需要在发送完频道后关闭它?
码:
1 2 3 4 5 6 7 8 9 | 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 close(c) // <- CLOSE IT HERE wg.Wait() } |
您的问题是您正在使用
1 2 3 4 5 6 7 8 9 10 | go func() { for { select { case x := <-c: fmt.Println("Read", x) case <-ti: fmt.Println("TIMED OUT") } } }() |
可以使用select关键字从不同的并发执行的goroutine中获取值,该关键字与switch控制语句非常相似,有时也称为通讯switch。
在带有默认大小写的select语句中使用send操作可确保发送将是非阻塞的!如果没有情况,则选择将永远阻止执行。
https://play.golang.org/p/Ai1ggveb4s