簡體   English   中英

如何始終從 Go 頻道獲取最新值?

[英]How to always get the latest value from a Go 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 秒后讀出最新值。

有沒有辦法讓一個通道一次總是只包含一個值,這樣我總是只得到我感興趣的數據,並且通道沒有使用不必要的內存(盡管內存是我最不擔心的)?

最好將通道視為隊列 (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 ,這意味着可以同時讀取很多東西,而只有一個東西可以寫入。 它將像您說的那樣存儲單個條目。

不。通道是 FIFO 緩沖區,句號。 這就是渠道的工作方式及其唯一目的。 如果您只想要最新的值,請考慮只使用受互斥鎖保護的單個變量; 每當有新數據進入時寫入它,無論何時讀取它,您將始終讀取最新值。

渠道服務於特定目的。 您可能希望使用鎖內的代碼並在要設置新值時更新變量。

這樣接收器將始終獲得最新的值。

您無法直接從一個通道獲取該值,但您可以為每個值使用一個通道,並在有新值時收到通知:

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

如果您不需要關於新值的通知,您可以嘗試閱讀Golang 中的 Channels inside channels 模式

試試這個包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

有一個優雅的僅通道解決方案。 如果您可以再添加一個通道和 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)
    }

}

還有另一種方法可以解決這個問題(技巧)

發件人工作更快:如果 channel_length > 1,發件人刪除頻道

go func() {
    for {
        msg:=strconv.Itoa(int(time.Now().Unix()))
        fmt.Println("make: ",msg," at:",time.Now())
        messages <- msg
        if len(messages)>1{
            //remove old message
            <-messages
        }
        time.Sleep(2*time.Second)
    }
}()

接收器工作較慢:

go func() {
    for {
        channLen :=len(messages)
        fmt.Println("len is ",channLen)
        fmt.Println("received",<-messages)
        time.Sleep(10*time.Second)
    }
}()

或者,我們可以從接收方刪除舊消息(閱讀消息就像刪除它一樣)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM