[英]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.