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

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

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)

r := &receiver{Chan: recv, list: &list}

go func() {

	for {
		// 当接收器为空时,从缓冲区发送下一个元素
		if len(recv) == 0 && len(list) > 0 {
			r.mutex.Lock()
			recv <- list[len(list)-1]
			list = list[:len(list)-1]
			r.mutex.Unlock()
		}

		select {
		// 将传入的元素添加到缓冲区
		case s := <-send:
			r.mutex.Lock()
			list = append(list, s)
			r.mutex.Unlock()
			//default:

		}
	}
}()

return r, send

}

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

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

// 向通道发送数据
go func() {
	for i := 0; i < 5; i++ {
		sender <- "Hi"
	}
	time.Sleep(time.Second)
	for i := 0; i < 5; i++ {
		sender <- "Bye"
	}
}()
time.Sleep(time.Millisecond * 70) // 等待接收每个 "Hi"
recv.Clear()
for data := range recv.Chan {
	println(data)
}

}

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

go func() {

for {
	select {
	// 将传入的元素添加到缓冲区
	case s := <-send:
		r.mutex.Lock()
		list = append(list, s)
		r.mutex.Unlock()
	default:
		// 当接收器为空时,从缓冲区发送下一个元素
		if len(recv) == 0 && len(list) > 0 {
			r.mutex.Lock()
			recv <- list[len(list)-1]
			list = list[:len(list)-1]
			r.mutex.Unlock()
		}
	}
}

}()

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

英文:

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

Here is the type declaration:

//receiver is the receiver of the non blocking channel
type receiver struct {
	Chan  &lt;-chan string
	list  *[]string
	mutex sync.RWMutex
}

//Clear sets the buffer to 0 elements
func (r *receiver) Clear() {
	r.mutex.Lock()
	*r.list = (*r.list)[:0]
	r.mutex.Unlock()
    //Discards residual content
	if len(r.Chan) == 1 {
		&lt;-r.Chan
	}
}

Constructor:

//NewNonBlockingChannel returns the receiver &amp; sender of a non blocking channel
func NewNonBlockingChannel() (*receiver, chan&lt;- string) {
	//Creates the send and receiver channels and the buffer
	send := make(chan string)
	recv := make(chan string, 1)
	list := make([]string, 0, 20)

	r := &amp;receiver{Chan: recv, list: &amp;list}

	go func() {

		for {
			//When the receiver is empty sends the next element from the buffer
			if len(recv) == 0 &amp;&amp; len(list) &gt; 0 {
				r.mutex.Lock()
				recv &lt;- list[len(list)-1]
				list = list[:len(list)-1]
				r.mutex.Unlock()
			}

			select {
			//Adds the incoming elements to the buffer
			case s := &lt;-send:
				r.mutex.Lock()
				list = append(list, s)
				r.mutex.Unlock()
				//default:

			}
		}
	}()

	return r, send
}

And the toy test in the main:

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

    //send data to the channel
	go func() {
		for i := 0; i &lt; 5; i++ {
			sender &lt;- &quot;Hi&quot;
		}
		time.Sleep(time.Second)
		for i := 0; i &lt; 5; i++ {
			sender &lt;- &quot;Bye&quot;
		}
	}()
	time.Sleep(time.Millisecond * 70) //waits to receive every &quot;Hi&quot;
	recv.Clear()
	for data := range recv.Chan {
		println(data)
	}

}

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:

go func() {

	for {
		select {
		//Adds the incoming elements to the buffer
		case s := &lt;-send:
			r.mutex.Lock()
			list = append(list, s)
			r.mutex.Unlock()
		default:
			//When the receiver is empty sends the next element from the buffer
			if len(recv) == 0 &amp;&amp; len(list) &gt; 0 {
				r.mutex.Lock()
				recv &lt;- list[len(list)-1]
				list = list[:len(list)-1]
				r.mutex.Unlock()
			}
		}
	}
}()

I'd like to know why.

答案1

得分: 0

假设有以下情景:

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

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

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

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

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

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

情况就不同了:

等待的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

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

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

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

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:

确定