我在这里找出执行后续任务最惯用的方式。
任务:
将数据从通道写入文件。
问题:
我有一个频道ch := make(chan int, 100)
我需要从通道读取并将从通道读取的值写入文件。我的问题基本上是如何做到这一点
ch
已满,请立即写入值ch
未满,则每5秒写一次。 基本上,数据需要至少每5秒写入文件一次(假设数据至少每5秒填充到通道中)
使用select
,for
和range
执行上述任务的最佳方法是什么?
谢谢!
答案 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
的缓冲区中没有更多值时,我们会返回。
由于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
}
如果我们坚持离开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()
}
我希望这会有所帮助。