当我将一个条件移到Go语言的select语句之外时,为什么会发生死锁?

huangapple go评论122阅读模式
英文:

Why does this deadlock happen when I move a condition outside the select statement in Go

问题

我需要一个具有动态缓冲区的非阻塞通道,用于一个项目,所以我写了这段代码。

这是类型声明:

// receiver 是非阻塞通道的接收器
type receiver struct {
Chan <-chan string
list *[]string
mutex sync.RWMutex
}

// Clear 将缓冲区设置为0个元素
func (r *receiver) Clear() {
r.mutex.Lock()
*r.list = (*r.list)[:0]
r.mutex.Unlock()
// 丢弃剩余内容
if len(r.Chan) == 1 {
<-r.Chan
}
}

构造函数:

// NewNonBlockingChannel 返回非阻塞通道的接收器和发送器
func NewNonBlockingChannel() (*receiver, chan<- string) {
// 创建发送和接收通道以及缓冲区
send := make(chan string)
recv := make(chan string, 1)
list := make([]string, 0, 20)

  1. r := &receiver{Chan: recv, list: &list}
  2. go func() {
  3. for {
  4. // 当接收器为空时,从缓冲区发送下一个元素
  5. if len(recv) == 0 && len(list) > 0 {
  6. r.mutex.Lock()
  7. recv <- list[len(list)-1]
  8. list = list[:len(list)-1]
  9. r.mutex.Unlock()
  10. }
  11. select {
  12. // 将传入的元素添加到缓冲区
  13. case s := <-send:
  14. r.mutex.Lock()
  15. list = append(list, s)
  16. r.mutex.Unlock()
  17. //default:
  18. }
  19. }
  20. }()
  21. return r, send

}

还有主函数中的测试代码:

func main() {
recv, sender := NewNonBlockingChannel()

  1. // 向通道发送数据
  2. go func() {
  3. for i := 0; i < 5; i++ {
  4. sender <- "Hi"
  5. }
  6. time.Sleep(time.Second)
  7. for i := 0; i < 5; i++ {
  8. sender <- "Bye"
  9. }
  10. }()
  11. time.Sleep(time.Millisecond * 70) // 等待接收每个 "Hi"
  12. recv.Clear()
  13. for data := range recv.Chan {
  14. println(data)
  15. }

}

当我测试时,在接收来自发送器的 "case s := <-send:" 的 select 语句中发生了死锁,但是当我将发送下一个缓冲字符串的条件块移到 select 语句中时,一切都正常工作:

go func() {

  1. for {
  2. select {
  3. // 将传入的元素添加到缓冲区
  4. case s := <-send:
  5. r.mutex.Lock()
  6. list = append(list, s)
  7. r.mutex.Unlock()
  8. default:
  9. // 当接收器为空时,从缓冲区发送下一个元素
  10. if len(recv) == 0 && len(list) > 0 {
  11. r.mutex.Lock()
  12. recv <- list[len(list)-1]
  13. list = list[:len(list)-1]
  14. r.mutex.Unlock()
  15. }
  16. }
  17. }

}()

我想知道为什么会出现这种情况。

英文:

I need a non blocking channel with dynamic buffer for a project so I wrote this code.

Here is the type declaration:

  1. //receiver is the receiver of the non blocking channel
  2. type receiver struct {
  3. Chan &lt;-chan string
  4. list *[]string
  5. mutex sync.RWMutex
  6. }
  7. //Clear sets the buffer to 0 elements
  8. func (r *receiver) Clear() {
  9. r.mutex.Lock()
  10. *r.list = (*r.list)[:0]
  11. r.mutex.Unlock()
  12. //Discards residual content
  13. if len(r.Chan) == 1 {
  14. &lt;-r.Chan
  15. }
  16. }

Constructor:

  1. //NewNonBlockingChannel returns the receiver &amp; sender of a non blocking channel
  2. func NewNonBlockingChannel() (*receiver, chan&lt;- string) {
  3. //Creates the send and receiver channels and the buffer
  4. send := make(chan string)
  5. recv := make(chan string, 1)
  6. list := make([]string, 0, 20)
  7. r := &amp;receiver{Chan: recv, list: &amp;list}
  8. go func() {
  9. for {
  10. //When the receiver is empty sends the next element from the buffer
  11. if len(recv) == 0 &amp;&amp; len(list) &gt; 0 {
  12. r.mutex.Lock()
  13. recv &lt;- list[len(list)-1]
  14. list = list[:len(list)-1]
  15. r.mutex.Unlock()
  16. }
  17. select {
  18. //Adds the incoming elements to the buffer
  19. case s := &lt;-send:
  20. r.mutex.Lock()
  21. list = append(list, s)
  22. r.mutex.Unlock()
  23. //default:
  24. }
  25. }
  26. }()
  27. return r, send
  28. }

And the toy test in the main:

  1. func main() {
  2. recv, sender := NewNonBlockingChannel()
  3. //send data to the channel
  4. go func() {
  5. for i := 0; i &lt; 5; i++ {
  6. sender &lt;- &quot;Hi&quot;
  7. }
  8. time.Sleep(time.Second)
  9. for i := 0; i &lt; 5; i++ {
  10. sender &lt;- &quot;Bye&quot;
  11. }
  12. }()
  13. time.Sleep(time.Millisecond * 70) //waits to receive every &quot;Hi&quot;
  14. recv.Clear()
  15. for data := range recv.Chan {
  16. println(data)
  17. }
  18. }

When I tested it and a deadlock happened in the select statement when I receive from the sender "case s := <-send:" but when I move the conditional block which sends the next buffered string into the select statement everything works perfectly:

  1. go func() {
  2. for {
  3. select {
  4. //Adds the incoming elements to the buffer
  5. case s := &lt;-send:
  6. r.mutex.Lock()
  7. list = append(list, s)
  8. r.mutex.Unlock()
  9. default:
  10. //When the receiver is empty sends the next element from the buffer
  11. if len(recv) == 0 &amp;&amp; len(list) &gt; 0 {
  12. r.mutex.Lock()
  13. recv &lt;- list[len(list)-1]
  14. list = list[:len(list)-1]
  15. r.mutex.Unlock()
  16. }
  17. }
  18. }
  19. }()

I'd like to know why.

答案1

得分: 0

假设有以下情景:

主程序发送了五个"bye",其中两个被打印出来:

  1. for data := range recv.Chan {
  2. println(data)
  3. }

剩下的三个被留在了list中(这是因为进入了三次for循环,条件len(recv) == 0 && len(list) > 0为假,所以只满足了case s := <-send,并且将s追加到了list中三次)。

而由NewNonBlockingChannel启动的goroutine正在等待语句s := <-send。没有更多的数据可以接收,所以它将永远等待下去。

当你将以下代码移动到select语句中时:

  1. if len(recv) == 0 && len(list) > 0 {
  2. r.mutex.Lock()
  3. recv <- list[len(list)-1]
  4. list = list[:len(list)-1]
  5. r.mutex.Unlock()
  6. }

情况就不同了:

等待的goroutine将被唤醒,并且条件if len(recv) == 0 && len(list) > 0恰好为真。一切都进行得很顺利。

英文:

Imagine this scenario for example:

five "bye" has been send by the main, two of them has been print by

  1. for data := range recv.Chan {
  2. println(data)
  3. }

three has been left in list (this is because three times enter for loop, the condition len(recv) == 0 &amp;&amp; len(list) &gt; 0 is false, so it just meet the case s := &lt;-send, and do list = append(list, s) three times)

and the goroutine started by NewNonBlockingChannel is waiting on the statment s := &lt;-send. no more staff to be receive, so it will wait forever

when you move the code

  1. if len(recv) == 0 &amp;&amp; len(list) &gt; 0 {
  2. r.mutex.Lock()
  3. recv &lt;- list[len(list)-1]
  4. list = list[:len(list)-1]
  5. r.mutex.Unlock()
  6. }

into the select, things are different:

the waiting gorountine will wake up, and the condition if len(recv) == 0 &amp;&amp; len(list) &gt; 0 is ture just in time. things goes well.

huangapple
  • 本文由 发表于 2016年12月16日 21:30:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/41185542.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定