英文:
How to signal if a value has been read from a channel in Go
问题
我正在阅读通过无限循环for
放入通道ch
的值。我希望有一种方法来表示是否已读取并处理了一个值(通过sq
结果),并在成功时将其添加到某种counter
变量中。这样我就可以检查我的通道是否已耗尽,以便可以正确退出无限循环for
。
目前,无论是否读取了一个值,它都在递增,因此当counter == num
时,它会提前退出。我只希望在值被平方时计数。
编辑:我测试过的另一种方法是在读取和设置val
时从通道中接收ok
val
,然后检查!ok { break }
。然而,我收到了死锁恐慌,因为for
没有正确地break
。示例在这里:https://go.dev/play/p/RYNtTix2nm2
package main
import "fmt"
func main() {
num := 5
// 带有5个值的缓冲通道。
ch := make(chan int, num)
defer close(ch)
for i := 0; i < num; i++ {
go func(val int) {
fmt.Printf("将值:%d 添加到通道\n", val)
ch <- val
}(i)
}
// 无限循环从通道中读取,并在每次读取和操作一个值时递增计数器
counter := 0
for {
// 检查计数器,如果等于num,则中断无限循环
if counter == num {
break
}
val := <-ch
counter++
go func(i int) {
// 我希望在递增计数器之前验证是否从ch中读取了一个值并进行了处理
sq := i * i
fmt.Println(sq)
}(val)
}
}
英文:
I am reading values that are put into a channel ch
via an infinite for
. I would like some way to signal if a value has been read and operated upon (via the sq
result) and add it to some sort of counter
variable upon success. That way I have a way to check if my channel has been exhausted so that I can properly exit my infinite for
loop.
Currently it is incrementing regardless if a value was read, thus causing it to exit early when the counter == num
. I only want it to count when the value has been squared.
EDIT: Another approach I have tested is to receive the ok
val
out of the channel upon reading and setting val
and then check if !ok { break }. However I receive a deadlock panic since the for
did has not properly break
. Example here: https://go.dev/play/p/RYNtTix2nm2
package main
import "fmt"
func main() {
num := 5
// Buffered channel with 5 values.
ch := make(chan int, num)
defer close(ch)
for i := 0; i < num; i++ {
go func(val int) {
fmt.Printf("Added value: %d to the channel\n", val)
ch <- val
}(i)
}
// Read from our channel infinitely and increment each time a value has been read and operated upon
counter := 0
for {
// Check our counter and if its == num then break the infinite loop
if counter == num {
break
}
val := <-ch
counter++
go func(i int) {
// I'd like to verify a value was read from ch & it was processed before I increment the counter
sq := i * i
fmt.Println(sq)
}(val)
}
}
答案1
得分: 2
让我来帮助你解决这个问题。
读取问题
你在问题中提供的代码的最新版本在读取ch
通道的值时出现了问题。我指的是以下代码片段:
go func(i int) {
// 我想要验证是否从ch通道中读取了一个值,并且在我递增计数器之前对其进行了处理
sq := i * i
fmt.Println(sq)
}(val)
实际上,并不需要为每次读取生成一个新的goroutine。你可以在ch
通道中的消息到达后立即消费它们。这是由于在goroutine内部进行的写入操作。多亏了它们,代码可以继续执行并达到读取阶段,而不会被阻塞。
有缓冲 vs 无缓冲
在这种情况下,你使用了一个带有5个数据槽的有缓冲通道。然而,如果你依赖于有缓冲通道,你应该在发送完数据后发出一个close(ch)
调用来表示发送数据的结束。这是在所有的goroutine完成任务后进行的。如果你使用的是无缓冲通道,可以在通道初始化旁边使用defer close(ch)
。实际上,这是为了清理和资源优化任务而进行的。回到你的例子,你可以改变实现方式,使用无缓冲通道。
最终代码
简要回顾一下,你需要做的两个小改变是:
- 使用无缓冲通道而不是有缓冲通道。
- 在从通道中读取消息时不要使用goroutine。
请确保准确理解正在发生的事情。另一个提示是发出语句:fmt.Println("NumGoroutine:", runtime.NumGoroutine())
,以打印在特定时刻正在运行的goroutine的确切数量。
最终代码如下:
package main
import (
"fmt"
"runtime"
)
func main() {
num := 5
// 无缓冲通道
ch := make(chan int)
defer close(ch)
for i := 0; i < num; i++ {
go func(val int) {
fmt.Printf("Added value: %d to the channel\n", val)
ch <- val
}(i)
}
fmt.Println("NumGoroutine:", runtime.NumGoroutine())
// 无限循环从通道中读取,并在每次读取和操作一个值后递增计数器
counter := 0
for {
// 检查计数器是否等于num,如果是则跳出无限循环
if counter == num {
break
}
val := <-ch
counter++
func(i int) {
// 我想要验证是否从ch通道中读取了一个值,并且在我递增计数器之前对其进行了处理
sq := i * i
fmt.Println(sq)
}(val)
}
}
希望对你有帮助,谢谢!
英文:
let me try to help you in figuring out the issue.
Reading issue
The latest version of the code you put in the question is working except when you're about to read values from the ch
channel. I mean with the following code snippet:
go func(i int) {
// I'd like to verify a value was read from ch & it was processed before I increment the counter
sq := i * I
fmt.Println(sq)
}(val)
In fact, it's not needed to spawn a new goroutine for each read. You can consume the messages as soon as they arrived in the ch
channel. This is possible due to writing done inside goroutines. Thanks to them, the code can go ahead and reach the reading phase without being blocked.
Buffered vs unbuffered
In this scenario, you used a buffered channel with 5 slots for data. However, if you're relying on the buffered channel you should signal when you finish sending data to it. This is done with a close(ch)
invocation after all of the Go routines finish their job. If you use an unbuffered channel it's fine to invoke defer close(ch)
next to the channel initialization. In fact, this is done for cleanup and resource optimization tasks. Back to your example, you can change the implementation to use unbuffered channels.
Final Code
Just to recap, the two small changes that you've to do are:
- Use an unbuffered channel instead of a buffered one.
- Do Not use a Go routine when reading the messages from the channel.
Please be sure to understand exactly what's going on. Another tip can be to issue the statement: fmt.Println("NumGoroutine:", runtime.NumGoroutine())
to print the exact number of Go routines running in that specific moment.
The final code:
package main
import (
"fmt"
"runtime"
)
func main() {
num := 5
// Buffered channel with 5 values.
ch := make(chan int)
defer close(ch)
for i := 0; i < num; i++ {
go func(val int) {
fmt.Printf("Added value: %d to the channel\n", val)
ch <- val
}(i)
}
fmt.Println("NumGoroutine:", runtime.NumGoroutine())
// Read from our channel infinitely and increment each time a value has been read and operated upon
counter := 0
for {
// Check our counter and if its == num then break the infinite loop
if counter == num {
break
}
val := <-ch
counter++
func(i int) {
// I'd like to verify a value was read from ch & it was processed before I increment the counter
sq := i * i
fmt.Println(sq)
}(val)
}
}
Let me know if this helps you, thanks!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论