假设一个频道有10个发送者和一个接收者。发送方功能需要一些时间才能返回值。接收器只需要来自通道的一个值(接收的第一个值),而不使用其他9个值。接收器不需要等待其余9个值。这就是为什么我没有使用sync.WaitGroup
。
我使用了缓冲通道,因此当接收器只接收第一个时,缓冲通道中将有9个数据。我的问题是:
没有接收器时,是否可以保留数据打开的缓冲通道?下面的示例代码是简单的,但如果程序是一个守护进程,它最终会被垃圾收集吗?
有没有更好的方法来处理这种情况?我试图使用取消频道但失败了。而且我不确定context
是否适合这种情况。
示例代码:
package main
import (
"errors"
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
i, err := getRandomInt()
if err != nil {
fmt.Println(err)
} else {
fmt.Println(i)
}
fmt.Println("Waiting goroutines to be finished...")
time.Sleep(2 * time.Second)
}
func getRandomInt() (int, error) {
ch := make(chan int, 10)
// 10 senders
for i := 0; i < 10; i++ {
go func(i int) {
defer fmt.Printf("Goroutine #%d finished\n", i)
fmt.Printf("Goroutine #%d started\n", i)
data := heavyJob()
ch <- data
fmt.Printf("Goroutine #%d sent data %d to ch\n", i, data)
return
}(i)
}
// 1 receiver
timeout := time.After(2000 * time.Millisecond)
for {
select {
case value := <-ch:
// uses only the value received first, the rest are discarded
return value, nil
case <-timeout:
return -1, errors.New("Timeout")
}
}
}
// takes 1900~2900ms to finish
func heavyJob() int {
r := rand.Intn(1000)
time.Sleep(time.Duration(r+1900) * time.Millisecond)
return r
}
答案 0 :(得分:2)
回答主要问题:
基本上,您在工作者数量和缓冲通道大小之间创建隐式耦合。改变这两个数字中的一个,一些东西会死锁/破坏! (作为旁注,缓冲通道通常用于消费者和生产者以相同的速率工作的情况,但是一个没有稳定的输出。它很尖锐,缓冲平滑了峰值和谷值。)
考虑到这一点,我建议显式更好地管理你不想要所有值的事实。
这是getRandomInt()函数的更新版本。请注意在顶部使用defer设置上下文取消,以及在send上使用select语句。
func getRandomInt() (int, error) {
ctx := context.Background() // creates a fresh, empty context
ctx, cancel := context.WithCancel(ctx)
defer cancel() // cancels the context when getRandomInt() returns
ch := make(chan int)
// 10 senders
for i := 0; i < 10; i++ {
go func(i int) {
defer fmt.Printf("Goroutine #%d finished\n", i)
fmt.Printf("Goroutine #%d started\n", i)
data := heavyJob()
// this select statement wil block until either this goroutine
// is the first to send, or the context is cancelled. In which case
// another routine has already sent and it can discard it's values.
select {
case ch <- data:
fmt.Printf("Goroutine #%d sent data %d to ch\n", i, data)
case <-ctx.Done():
fmt.Printf("Goroutine #%d did not send, context is cancelled, would have sent data %d to ch\n", i, data)
}
}(i)
}
// 1 receiver
timeout := time.After(2000 * time.Millisecond)
select {
case value := <-ch:
// uses only the value received first, the rest are discarded
return value, nil
case <-timeout:
return -1, errors.New("Timeout")
}
}
使用取消设置上下文意味着一旦调用cancel()
函数,上下文就变为“完成”。这是告诉所有发件人goroutines不要等待发送的方法。
在发送时,select语句将阻塞,直到cancel()
函数取消上下文为止;或接收方法读取第一个值。
我还删除了频道的缓冲,因为不再需要它了。