英文:
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 <-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 {
<-r.Chan
}
}
Constructor:
//NewNonBlockingChannel returns the receiver & sender of a non blocking channel
func NewNonBlockingChannel() (*receiver, chan<- 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 := &receiver{Chan: recv, list: &list}
go func() {
for {
//When the receiver is empty sends the next element from the buffer
if len(recv) == 0 && len(list) > 0 {
r.mutex.Lock()
recv <- list[len(list)-1]
list = list[:len(list)-1]
r.mutex.Unlock()
}
select {
//Adds the incoming elements to the buffer
case s := <-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 < 5; i++ {
sender <- "Hi"
}
time.Sleep(time.Second)
for i := 0; i < 5; i++ {
sender <- "Bye"
}
}()
time.Sleep(time.Millisecond * 70) //waits to receive every "Hi"
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 := <-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 && len(list) > 0 {
r.mutex.Lock()
recv <- 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 && len(list) > 0
is false, so it just meet the case s := <-send
, and do list = append(list, s)
three times)
and the goroutine started by NewNonBlockingChannel
is waiting on the statment s := <-send
. no more staff to be receive, so it will wait forever
when you move the code
if len(recv) == 0 && len(list) > 0 {
r.mutex.Lock()
recv <- 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 && len(list) > 0
is ture just in time. things goes well.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论