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

时间:2016-07-18 06:48:13

标签: go channel

我在这里找出执行后续任务最惯用的方式。

任务

将数据从通道写入文件。

问题

我有一个频道ch := make(chan int, 100)

我需要从通道读取并将从通道读取的值写入文件。我的问题基本上是如何做到这一点

  1. 如果频道ch已满,请立即写入值
  2. 如果频道ch未满,则每5秒写一次。
  3. 基本上,数据需要至少每5秒写入文件一次(假设数据至少每5秒填充到通道中)

    使用selectforrange执行上述任务的最佳方法是什么?

    谢谢!

2 个答案:

答案 0 :(得分:2)

没有这样的"事件"因为"频道缓冲区已满#34; ,所以你无法检测到[*]。这意味着您只需使用1个频道就可以通过语言原语来解决您的问题。

[*]不完全正确:在通道上发送的情况下,使用select default时,您可以检测到频道的缓冲区是否已满,但这需要发件人的逻辑,并重复尝试发送。

我会使用另一个频道,当我在其上发送值时,我会收到这些频道,并且"重定向",将值存储在另一个缓冲区为100的通道中,如上所述。在每次重定向时,您可以检查内部通道的缓冲区是否已满,如果是,则立即执行写入。如果没有,继续监控"传入"频道和带有select语句的计时器频道,如果计时器触发,请执行"常规"写。

您可以使用len(chInternal)来检查chInternal频道中的元素数量,以及cap(chInternal)来检查其容量。请注意,这是"安全"因为我们是唯一处理chInternal频道的goroutine。如果存在多个goroutine,len(chInternal)返回的值可能会在我们将其用于某些内容时过时(例如,比较它)。

在此解决方案中chInternal(正如其名称所示)仅供内部使用。其他人应该只在ch发送值。请注意,ch可能是也可能不是缓冲通道,解决方案适用于这两种情况。但是,如果您还为ch提供一些缓冲区,则可以提高效率(因此发件人被阻止的可能性会降低)。

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

如果发生立即写入(由于&#34;缓冲区已完全&#34;情况),此解决方案将为下一个&#34;常规&#34;在此之后写5秒。如果您不想要这个并且您希望5秒的常规写入与立即写入无关,则只需在立即写入后不重置定时器。

doWrite()的实施可能如下:

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

我们无法使用for ... range,因为只有在频道关闭时才返回,但我们的chInternal频道未关闭。因此,我们使用带有select大小写的default,因此当chInternal的缓冲区中没有更多值时,我们会返回。

改进

使用切片而不是第2个通道

由于chInternal频道仅由我们使用,并且仅在单个goroutine上使用,我们也可以选择使用单个[]int切片而不是频道(读取/写入切片很多比通道快。)

仅显示不同/已更改的部分,它可能如下所示:

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
}

有多个goroutine

如果我们坚持离开chInternal一个频道,可以在另一个goroutine上调用doWrite()函数,以便不阻止另一个,例如go doWrite()。由于要从通道(chInternal)读取要写入的数据,因此无需进一步同步。

答案 1 :(得分:0)

如果你只是用5秒写,增加文件写入性能,
您可以随时填写频道,
然后writer goroutine将数据写入缓冲文件,
看到这个非常简单和惯用的样本而不使用计时器
仅用于...范围

package main

import (
    "bufio"
    "fmt"
    "os"
    "sync"
)

var wg sync.WaitGroup

func WriteToFile(filename string, ch chan int) {
    f, e := os.Create(filename)
    if e != nil {
        panic(e)
    }
    w := bufio.NewWriterSize(f, 4*1024*1024)
    defer wg.Done()
    defer f.Close()
    defer w.Flush()
    for v := range ch {
        fmt.Fprintf(w, "%d ", v)
    }
}

func main() {
    ch := make(chan int, 100)
    wg.Add(1)
    go WriteToFile("file.txt", ch)

    for i := 0; i < 500000; i++ {
        ch <- i // do the job
    }
    close(ch) // Finish the job and close output file
    wg.Wait()
}

并注意defer的订单。

如果写入5秒,您可以添加一个间隔计时器,只是为了将此文件的缓冲区刷新到磁盘,如下所示:

package main

import (
    "bufio"
    "fmt"
    "os"
    "sync"
    "time"
)

var wg sync.WaitGroup

func WriteToFile(filename string, ch chan int) {
    f, e := os.Create(filename)
    if e != nil {
        panic(e)
    }
    w := bufio.NewWriterSize(f, 4*1024*1024)

    ticker := time.NewTicker(5 * time.Second)
    quit := make(chan struct{})
    go func() {
        for {
            select {
            case <-ticker.C:
                if w.Buffered() > 0 {
                    fmt.Println(w.Buffered())
                    w.Flush()
                }
            case <-quit:
                ticker.Stop()
                return
            }
        }
    }()

    defer wg.Done()
    defer f.Close()
    defer w.Flush()
    defer close(quit)
    for v := range ch {
        fmt.Fprintf(w, "%d ", v)
    }
}

func main() {
    ch := make(chan int, 100)
    wg.Add(1)
    go WriteToFile("file.txt", ch)

    for i := 0; i < 25; i++ {
        ch <- i // do the job
        time.Sleep(500 * time.Millisecond)
    }
    close(ch) // Finish the job and close output file
    wg.Wait()
}

此处我使用time.NewTicker(5 * time.Second)用于quit频道的间隔计时器,您可以使用time.AfterFunc()time.Tick()time.Sleep()

进行一些优化(删除退出渠道):

package main

import (
    "bufio"
    "fmt"
    "os"
    "sync"
    "time"
)

var wg sync.WaitGroup

func WriteToFile(filename string, ch chan int) {
    f, e := os.Create(filename)
    if e != nil {
        panic(e)
    }
    w := bufio.NewWriterSize(f, 4*1024*1024)
    ticker := time.NewTicker(5 * time.Second)
    defer wg.Done()
    defer f.Close()
    defer w.Flush()

    for {
        select {
        case v, ok := <-ch:
            if ok {
                fmt.Fprintf(w, "%d ", v)
            } else {
                fmt.Println("done.")
                ticker.Stop()
                return
            }
        case <-ticker.C:
            if w.Buffered() > 0 {
                fmt.Println(w.Buffered())
                w.Flush()
            }
        }
    }
}
func main() {
    ch := make(chan int, 100)
    wg.Add(1)
    go WriteToFile("file.txt", ch)

    for i := 0; i < 25; i++ {
        ch <- i // do the job
        time.Sleep(500 * time.Millisecond)
    }
    close(ch) // Finish the job and close output file
    wg.Wait()
}

我希望这会有所帮助。