英文:
How to block in the select default case in golang?
问题
我正在将数据写入网络。
写入的 goroutine 如下所示。
forend:
for {
select {
case buf, ok := <-sendqueue:
if !ok {
break forend
}
writeBuffer(conn, buf)
}
}
变量 conn 是一个 net.Conn。
然后我想使用 bufio 替换 net.Conn。
iowriter := bufio.NewWriter(conn)
iowriter 将缓存数据。为了减少延迟,当 sendqueue 中没有更多数据时,我必须立即刷新 iowriter。
因此,我在写入的 goroutine 中添加了一个默认情况。
forend:
for {
select {
case buf, ok := <-sendqueue:
if !ok {
break forend
}
writeBuffer(iowriter, buf)
default:
iowriter.Flush()
time.Sleep(time.Millisecond)
}
}
time.Sleep 是必要的,否则 goroutine 将一直运行忙循环。
但在这种情况下,真正的需求是阻塞而不是休眠。
最后,我找到了一个解决方案,使用了两个 select。
forend:
for {
select {
case buf, ok := <-sendqueue:
if !ok {
break forend
}
writeBuffer(iowriter, buf)
}
nextloop:
for {
select {
case buf, ok := <-sendqueue:
if !ok {
break forend
}
writeBuffer(iowriter, buf)
default:
iowriter.Flush()
break nextloop
}
}
}
但这个解决方案比较复杂。第二个 select 与第一个 select 是重复的,只是多了一个默认情况。是否有更好的解决方案?
这里的行为是这样的:如果 sendqueue 不为空,我会继续弹出数据并发送。如果 sendqueue 为空,我希望立即刷新 iowriter 中缓存的数据,然后再次在 sendqueue 上等待。
这个行为可以抽象为:当通道为空时,我想做一些事情,然后再次在通道上阻塞等待。我找到了两个重复的 select 解决方案。但是当在多个通道上等待时,这个解决方案可能变得更加复杂。所以我正在寻找更好的解决方案。
<details>
<summary>英文:</summary>
I am writing data to network.
The writing goroutine is like this.
forend:
for{
select{
case buf,ok:=<-sendqueue:
if !ok{
break forend
}
writeBuffer(conn,buf)
}
}
The variable conn is a net.Conn.
Then I want to use bufio to replace net.Conn.
iowriter:=bufio.NewWriter(conn)
iowriter will cache the data.To reduce the delay,I must flush the iowriter immediately when there is no more data in sendqueue.
So I add a default case to the writing goroutine
forend:
for{
select{
case buf,ok:=<-sendqueue:
if !ok{
break forend
}
writeBuffer(iowriter,buf)
default:
iowriter.Flush()
time.Sleep(time.Millisecond)
}
}
The time.Sleep is necessary,otherwise the goroutine will run busy loop.
But in this case, the really demand is to block not to sleep.
Finally, I found a solution,with two selects.
forend:
for{
select{
case buf,ok:=<-sendqueue:
if !ok{
break forend
}
writeBuffer(iowriter,buf)
}
nextloop:
for{
select{
case buf,ok:=<-sendqueue:
if !ok{
break forend
}
writeBuffer(iowriter,buf)
default:
iowriter.Flush()
break nextloop
}
}
}
But this solution is complicate.The second select is the duplicate of the first with a default case.Is there any better solution?
~~~~~~~~~~~~~~~~~~~~~~~ More explanation
The behavior here is like this.If the sendqueue is not empty,I continue to pop data and send it.If the sendqueue is empty,I want to flush the data cached in iowriter immediately and then waiting on the sendqueue again.
This behavior can be abstract like this.I want to do something when channel is empty,and then waiting on channel again,with blocking.I found the two duplicate select solution.But that solution may become more complicated when waiting on more than one channel.So I am looking for a better solution.
</details>
# 答案1
**得分**: 6
你在使用两步解决方案方面是正确的。
由于你想要做两件事情:首先接收一个数据包(如果还没有可用的数据包,则阻塞),然后在有数据包并且全部消耗完毕后刷新,你不能使用单个`select`语句来实现这一点。
但是我们可以进行一些简化,第一个`select`语句是完全不必要的,因为它只有一个`case`分支:
```go
forend:
for {
buf, ok := <-sendqueue
if !ok {
break forend
}
writeBuffer(iowriter, buf)
nextloop:
for {
select {
case buf, ok := <-sendqueue:
if !ok {
break forend
}
writeBuffer(iowriter, buf)
default:
iowriter.Flush()
break nextloop
}
}
}
```
由于处理接收到的数据在两种情况下是相同的,你可以将该逻辑放入一个函数中以避免重复:
```go
var buf datatype
var ok = true
handle := func() {
if ok {
writeBuffer(iowriter, buf)
}
}
for ok {
buf, ok = <-sendqueue
handle()
nextloop:
for ok {
select {
case buf, ok = <-sendqueue:
handle()
default:
iowriter.Flush()
break nextloop
}
}
}
```
<details>
<summary>英文:</summary>
You're on the right track with the 2-step solution.
Since you want to do 2 things: first receive a packet (blocking if one is not available yet), and then keep receiving while there are packets and flush if consumed all, you can't do this with a single `select` statement.
But we can do some simplification, the first `select` statement is completely unnecessary, as it only has a single `case` branch:
forend:
for {
buf, ok := <-sendqueue
if !ok {
break forend
}
writeBuffer(iowriter, buf)
nextloop:
for {
select {
case buf, ok := <-sendqueue:
if !ok {
break forend
}
writeBuffer(iowriter, buf)
default:
iowriter.Flush()
break nextloop
}
}
}
Since handling the received data is the same in both cases, you can put that logic into a function to avoid repetition:
var buf datatype
var ok = true
handle := func() {
if ok {
writeBuffer(iowriter, buf)
}
}
for ok {
buf, ok = <-sendqueue
handle()
nextloop:
for ok {
select {
case buf, ok = <-sendqueue:
handle()
default:
iowriter.Flush()
break nextloop
}
}
}
</details>
# 答案2
**得分**: 0
我们可以借助一个定时器来防止循环不断刷新,代码如下:
```go
ticker := time.NewTicker(time.Millisecond * 10)
defer ticker.Stop()
for {
select {
case buf, ok := <-sendqueue:
if !ok {
return
}
writeBuffer(iowriter, buf)
case <-ticker.C:
iowriter.Flush()
}
}
```
现在,你的数据将在准备好后的最多10毫秒内发送到管道上。
<details>
<summary>英文:</summary>
We can prevent the loop from constantly flushing with the help of a ticker like so:
ticker := time.NewTicker(time.Millisecond * 10)
defer ticker.Stop()
for {
select {
case buf,ok:=<-sendqueue:
if !ok{
return
}
writeBuffer(iowriter,buf)
case <-ticker.C:
iowriter.Flush()
}
}
Your data will now be sent on the pipe at latest 10ms after it's ready.
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论