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.
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.
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
}
}
}