如何在Golang中阻止select语句的默认情况?

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

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:=&lt;-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:=&lt;-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:=&lt;-sendqueue:
            if !ok{
                break forend
            }
            writeBuffer(iowriter,buf)
        }
    nextloop:
        for{
            select{
            case buf,ok:=&lt;-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&#39;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&#39;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 := &lt;-sendqueue
    		if !ok {
    			break forend
    		}
    		writeBuffer(iowriter, buf)
    
    	nextloop:
    		for {
    			select {
    			case buf, ok := &lt;-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 = &lt;-sendqueue
		handle()

	nextloop:
		for ok {
			select {
			case buf, ok = &lt;-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:=&lt;-sendqueue:
                if !ok{
                    return
                }
                writeBuffer(iowriter,buf)
            case &lt;-ticker.C:
                   iowriter.Flush()
            }
    }

Your data will now be sent on the pipe at latest 10ms after it&#39;s ready.


</details>



huangapple
  • 本文由 发表于 2017年8月24日 11:31:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/45852480.html
匿名

发表评论

匿名网友

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

确定