如何始终从golang渠道获取最新价值?

时间:2018-12-13 22:05:44

标签: go concurrency parallel-processing multiprocessing channel

我从Go开始,现在正在编写一个简单的程序,该程序从传感器中读取数据并将其放入通道中以进行一些计算。现在,它的工作方式如下:

package main

import (
    "fmt"
    "time"
    "strconv"
)

func get_sensor_data(c chan float64) {
    time.Sleep(1 * time.Second)  // wait a second before sensor data starts pooring in
    c <- 2.1  // Sensor data starts being generated
    c <- 2.2
    c <- 2.3
    c <- 2.4
    c <- 2.5
}

func main() {

    s := 1.1

    c := make(chan float64)
    go get_sensor_data(c)

    for {
        select {
        case s = <-c:
            fmt.Println("the next value of s from the channel: " + strconv.FormatFloat(s, 'f', 1, 64))
        default:
            // no new values in the channel
        }
        fmt.Println(s)

        time.Sleep(500 * time.Millisecond)  // Do heavy "work"
    }
}

这可以正常工作,但是传感器会生成大量数据,而我始终只对最新数据感兴趣。但是,通过这种设置,它只会在每个循环中读出下一个项目,这意味着如果某个点的通道包含20个值,则仅在10秒后才读出最新值。

是否有一种方法可以使通道一次始终只包含一个值,所以我总是只获取自己感兴趣的数据,并且通道不使用不必要的内存(尽管内存是最少的)我的担心)?

欢迎所有提示!

6 个答案:

答案 0 :(得分:5)

最好将通道视为队列(FIFO)。因此,您不能真正跳过。但是,有一些库可以执行以下操作:https://github.com/cloudfoundry/go-diodes是一个原子环形缓冲区,它将覆盖旧数据。您可以根据需要设置较小的尺寸。

所有这些,听起来并不像您需要一个队列(或环形缓冲区)。您只需要一个互斥锁:

type SensorData struct{
  mu sync.RWMutex
  last float64
}

func (d *SensorData) Store(data float64) {
 mu.Lock()
 defer mu.Unlock()

 d.last = data
}

func (d *SensorData) Get() float64 {
 mu.RLock()
 defer mu.RUnlock()

 return d.last
}

这使用RWMutex,这意味着可以同时读取许多内容,而只能写入单个内容。它将像您说的那样存储一个条目。

答案 1 :(得分:4)

不。通道是FIFO缓冲区,全停。这就是渠道的工作方式及其唯一目的。如果只需要最新的值,请考虑只使用一个由互斥锁保护的变量。每当有新数据输入时,无论何时读取它,都将始终对其进行写操作。

答案 2 :(得分:1)

频道用于特定目的。您可能想使用锁中的代码,并在每次要设置新值时更新变量。

这样,接收者将始终获得最新的值。

答案 3 :(得分:0)

您不能直接从一个通道获取该值,但是每个值可以使用一个通道,并在有新值时得到通知:

package main

import (
    "fmt"
    "strconv"
    "sync"
    "time"
)

type LatestChannel struct {
    n    float64
    next chan struct{}
    mu   sync.Mutex
}

func New() *LatestChannel {
    return &LatestChannel{next: make(chan struct{})}
}

func (c *LatestChannel) Push(n float64) {
    c.mu.Lock()
    c.n = n
    old := c.next
    c.next = make(chan struct{})
    c.mu.Unlock()
    close(old)
}

func (c *LatestChannel) Get() (float64, <-chan struct{}) {
    c.mu.Lock()
    n := c.n
    next := c.next
    c.mu.Unlock()
    return n, next
}

func getSensorData(c *LatestChannel) {
    time.Sleep(1 * time.Second)
    c.Push(2.1)
    time.Sleep(100 * time.Millisecond)
    c.Push(2.2)
    time.Sleep(100 * time.Millisecond)
    c.Push(2.3)
    time.Sleep(100 * time.Millisecond)
    c.Push(2.4)
    time.Sleep(100 * time.Millisecond)
    c.Push(2.5)
}

func main() {
    s := 1.1

    c := New()
    _, hasNext := c.Get()
    go getSensorData(c)

    for {
        select {
        case <-hasNext:
            s, hasNext = c.Get()
            fmt.Println("the next value of s from the channel: " + strconv.FormatFloat(s, 'f', 1, 64))
        default:
            // no new values in the channel
        }
        fmt.Println(s)

        time.Sleep(250 * time.Millisecond) // Do heavy "work"
    }
}

如果您不需要有关新值的通知,则可以尝试阅读Channels inside channels pattern in Golang

答案 4 :(得分:0)

尝试使用此软件包https://github.com/subbuv26/chanup

它允许生产者使用最新值更新频道,从而替换最新值。并且生产不会被阻塞。 (由此,过时的值将被覆盖)。 因此,在消费者方面,总是只读取最新的项目。

import "github.com/subbuv26/chanup"
ch := chanup.GetChan()
_ := ch.Put(testType{
    a: 10,
    s: "Sample",
})
_ := ch.Update(testType{
    a: 20,
    s: "Sample2",
})
// Continue updating with latest values
...

...
// On consumer end
val := ch.Get()
// val contains latest value

答案 5 :(得分:0)

有一个优雅的仅频道解决方案。如果您可以再添加一个通道和 goroutine - 您可以引入一个无缓冲通道和一个 goroutine 来尝试将最新值从您的通道发送到它:

package main

import (
    "fmt"
    "time"
)

func wrapLatest(ch <-chan int) <-chan int {
    result := make(chan int) // important that this one i unbuffered
    go func() {
        defer close(result)
        value, ok := <-ch
        if !ok {
            return
        } 
        for {
            select {
            case value, ok = <-ch:
                if !ok {
                    return
                }
            case result<-value:
                if value, ok = <-ch; !ok {
                    return
                }
            }
        }
    }()

    return result
}

func main() {
    sendChan := make(chan int, 10) // may be buffered or not
    go func() {
        for i := 0; i < 10; i++ {
            sendChan <- i
        time.Sleep(time.Second)
        }
        close(sendChan)
    }()
    recvChan := wrapLatest(sendChan)
    for i := range recvChan {
        fmt.Println(i)
        time.Sleep(time.Second*2)
    }

}