dropbox1111 2016-07-18 06:48
浏览 44
已采纳

在时间间隔和通道长度之间进行选择

I'm here to find out the most idiomatic way to do the follow task.

Task:

Write data from a channel to a file.

Problem:

I have a channel ch := make(chan int, 100)

I need to read from the channel and write the values I read from the channel to a file. My question is basically how do I do so given that

  1. If channel ch is full, write the values immediately
  2. If channel ch is not full, write every 5s.

So essentially, data needs to be written to the file at least every 5s (assuming that data will be filled into the channel at least every 5s)

Whats the best way to use select, for and range to do my above task?

Thanks!

  • 写回答

2条回答 默认 最新

  • drvpv7995 2016-07-18 07:25
    关注

    There is no such "event" as "buffer of channel is full", so you can't detect that [*]. This means you can't idiomatically solve your problem with language primitives using only 1 channel.

    [*] Not entirely true: you could detect if the buffer of a channel is full by using select with default case when sending on the channel, but that requires logic from the senders, and repetitive attempts to send.

    I would use another channel from which I would receive as values are sent on it, and "redirect", store the values in another channel which has a buffer of 100 as you mentioned. At each redirection you may check if the internal channel's buffer is full, and if so, do an immediate write. If not, continue to monitor the "incoming" channel and a timer channel with a select statement, and if the timer fires, do a "regular" write.

    You may use len(chInternal) to check how many elements are in the chInternal channel, and cap(chInternal) to check its capacity. Note that this is "safe" as we are the only goroutine handling the chInternal channel. If there would be multiple goroutines, value returned by len(chInternal) could be outdated by the time we use it to something (e.g. comparing it).

    In this solution chInternal (as its name says) is for internal use only. Others should only send values on ch. Note that ch may or may not be a buffered channel, solution works in both cases. However, you may improve efficiency if you also give some buffer to ch (so chances that senders get blocked will be lower).

    var (
        chInternal = make(chan int, 100)
        ch         = make(chan int) // You may (should) make this a buffered channel too
    )
    
    func main() {
        delay := time.Second * 5
        timer := time.NewTimer(delay)
        for {
            select {
            case v := <-ch:
                chInternal <- v
                if len(chInternal) == cap(chInternal) {
                    doWrite() // Buffer is full, we need to write immediately
                    timer.Reset(delay)
                }
            case <-timer.C:
                doWrite() // "Regular" write: 5 seconds have passed since last write
                timer.Reset(delay)
            }
        }
    }
    

    If an immediate write happens (due to a "buffer full" situation), this solution will time the next "regular" write 5 seconds after this. If you don't want this and you want the 5-second regular writes be independent from the immediate writes, simply do not reset the timer following the immediate write.

    An implementation of doWrite() may be as follows:

    var f *os.File // Make sure to open file for writing
    
    func doWrite() {
        for {
            select {
            case v := <-chInternal:
                fmt.Fprintf(f, "%d ", v) // Write v to the file
            default: // Stop when no more values in chInternal
                return
            }
        }
    }
    

    We can't use for ... range as that only returns when the channel is closed, but our chInternal channel is not closed. So we use a select with a default case so when no more values are in the buffer of chInternal, we return.

    Improvements

    Using a slice instead of 2nd channel

    Since the chInternal channel is only used by us, and only on a single goroutine, we may also choose to use a single []int slice instead of a channel (reading/writing a slice is much faster than a channel).

    Showing only the different / changed parts, it could look something like this:

    var (
        buf = make([]int, 0, 100)
    )
    
    func main() {
        // ...
    
        for {
            select {
            case v := <-ch:
                buf = append(buf, v)
                if len(buf) == cap(buf) {
                // ...
        }
    }
    
    func doWrite() {
        for _, v := range buf {
            fmt.Fprintf(f, "%d ", v) // Write v to the file
        }
        buf = buf[:0] // "Clear" the buffer
    }
    

    With multiple goroutines

    If we stick to leave chInternal a channel, the doWrite() function may be called on another goroutine to not block the other one, e.g. go doWrite(). Since data to write is read from a channel (chInternal), this requires no further synchronization.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥20 机器学习能否像多层线性模型一样处理嵌套数据
  • ¥20 西门子S7-Graph,S7-300,梯形图
  • ¥50 用易语言http 访问不了网页
  • ¥50 safari浏览器fetch提交数据后数据丢失问题
  • ¥15 matlab不知道怎么改,求解答!!
  • ¥15 永磁直线电机的电流环pi调不出来
  • ¥15 用stata实现聚类的代码
  • ¥15 请问paddlehub能支持移动端开发吗?在Android studio上该如何部署?
  • ¥20 docker里部署springboot项目,访问不到扬声器
  • ¥15 netty整合springboot之后自动重连失效