英文:
Reading from a goroutine channel without blocking
问题
我有两个goroutine:主要的worker
和一个helper
,worker
会为其提供一些帮助。helper
可能会遇到错误,所以我使用一个通道来将错误从helper
传递给worker
。
func helper(c chan<- error) {
//做一些工作
c <- err // 在c上发送错误/nil
}
这是如何调用helper()
的:
func worker() error {
//做一些工作
c := make(chan error, 1)
go helper(c)
err := <-c
return err
}
问题:
-
语句
err := <-c
是否会阻塞worker
?我认为不会,因为通道是有缓冲的。 -
如果它是阻塞的,我如何使其非阻塞?我的要求是让
worker
及其调用者继续进行其余的工作,而不是“等待”值出现在通道上。
谢谢。
英文:
I have two goroutines: the main worker
and a helper
that it spins off for some help. helper
can encounter errors, so I use a channel to communicate errors over from the helper
to the worker
.
func helper(c chan <- error) (){
//do some work
c <- err // send errors/nil on c
}
Here is how helper()
is called:
func worker() error {
//do some work
c := make(chan error, 1)
go helper(c)
err := <- c
return err
}
Questions:
-
Is the statement
err := <- c
blockingworker
? I don't think so, since the channel is buffered. -
If it is blocking, how do I make it non-blocking? My requirement is to have
worker
and its caller continue with rest of the work, without waiting for the value to appear on the channel.
Thanks.
答案1
得分: 4
你可以轻松验证
func helper(c chan<- error) {
time.Sleep(5 * time.Second)
c <- errors.New("")
}
func worker() error {
fmt.Println("do one")
c := make(chan error, 1)
go helper(c)
err := <-c
fmt.Println("do two")
return err
}
func main() {
worker()
}
问:语句 err := <-c 是否会阻塞 worker?我认为不会,因为通道是有缓冲的。
答: err := <-c
会阻塞 worker。
问:如果它是阻塞的,我如何使其非阻塞?我的要求是让 worker 及其调用者继续执行其余的工作,而不等待通道上的值出现。
答: 如果你不想阻塞,只需移除 err := <-c
。如果你需要在最后获取 err,只需将 err := <-c
移到最后。
你不能在不阻塞的情况下读取通道,如果你继续执行而不阻塞,除非你的代码在循环中。
Loop:
for {
select {
case <-c:
break Loop
default:
// default 会在不阻塞的情况下执行
}
// 做一些其他操作
}
你见过 errgroup 或 waitgroup 吗?
它使用原子操作、取消上下文和 sync.Once 来实现。
https://github.com/golang/sync/blob/master/errgroup/errgroup.go
https://github.com/golang/go/blob/master/src/sync/waitgroup.go
或者你可以直接使用它,在任何你想要的地方启动 goroutine,然后等待错误发生。
英文:
You can easily verify
func helper(c chan<- error) {
time.Sleep(5 * time.Second)
c <- errors.New("") // send errors/nil on c
}
func worker() error {
fmt.Println("do one")
c := make(chan error, 1)
go helper(c)
err := <-c
fmt.Println("do two")
return err
}
func main() {
worker()
}
> Q: Is the statement err := <- c blocking worker? I don't think so, since the channel is buffered.
A: err := <- c
will block worker.
> Q: If it is blocking, how do I make it non-blocking? My requirement is to have worker and its caller continue with rest of the work, without waiting for the value to appear on the channel.
A: If you don't want blocking, just remove err := <-c
. If you need err at the end, just move err := <-c
to the end.
You can not read channel without blocking, if you go through without blocking, can can no more exec this code, unless your code is in a loop.
Loop:
for {
select {
case <-c:
break Loop
default:
//default will go through without blocking
}
// do something
}
And have you ever seen errgroup or waitgroup?
It use atomic, cancel context and sync.Once to implement this.
https://github.com/golang/sync/blob/master/errgroup/errgroup.go
https://github.com/golang/go/blob/master/src/sync/waitgroup.go
Or you can just use it, go you func and then wait for error in any place you want.
答案2
得分: 1
在你的代码中,剩下的工作与助手是否遇到错误无关。在完成剩下的工作后,你可以简单地从通道接收。
func worker() error {
//做一些工作
c := make(chan error, 1)
go helper(c)
//完成剩下的工作
return <-c
}
英文:
In your code, the rest of the work is independent of whether the helper encountered an error. You can simply receive from the channel after the rest of the work is completed.
func worker() error {
//do some work
c := make(chan error, 1)
go helper(c)
//do rest of the work
return <-c
}
答案3
得分: 0
我认为你需要这段代码:
运行这段代码:
package main
import (
"log"
"sync"
)
func helper(c chan<- error) {
for {
var err error = nil
// do job
if err != nil {
c <- err // send errors/nil on c
break
}
}
}
func worker(c chan error) error {
log.Println("第一个日志")
go func() {
helper(c)
}()
count := 1
Loop:
for {
select {
case err := <-c:
return err
default:
log.Println(count, "日志")
count++
isFinished := false
// do your job
if isFinished {
break Loop // remove this when you test
}
}
}
return nil
}
func main() {
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
c := make(chan error, 1)
worker(c)
wg.Done()
}()
wg.Wait()
}
希望对你有帮助!
英文:
I think you need this code..
run this code
package main
import (
"log"
"sync"
)
func helper(c chan<- error) {
for {
var err error = nil
// do job
if err != nil {
c <- err // send errors/nil on c
break
}
}
}
func worker(c chan error) error {
log.Println("first log")
go func() {
helper(c)
}()
count := 1
Loop:
for {
select {
case err := <- c :
return err
default:
log.Println(count, " log")
count++
isFinished := false
// do your job
if isFinished {
break Loop // remove this when you test
}
}
}
return nil
}
func main() {
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
c := make(chan error, 1)
worker(c)
wg.Done()
}()
wg.Wait()
}
答案4
得分: 0
以非阻塞方式从通道中读取的函数式方法:
func CollectChanOne[T any](ch <-chan T) (T, bool) {
select {
case val, stillOpen := <-ch:
return val, stillOpen
default:
var zeroT T
return zeroT, false
}
}
func worker() error {
// 做一些工作
c := make(chan error, 1)
go helper(c)
err, _ := CollectChanOne(c) // 不会阻塞 worker
return err
}
示例:https://go.dev/play/p/Njwyt32B4oT
注意:此示例还有另一种方法 CollectChanRemaining(),它会读取通道中的所有缓冲元素。
英文:
Functional way of reading from a channel in a non-blocking manner:
func CollectChanOne[T any](ch <-chan T) (T, bool) {
select {
case val, stillOpen := <-ch:
return val, stillOpen
default:
var zeroT T
return zeroT, false
}
}
func worker() error {
//do some work
c := make(chan error, 1)
go helper(c)
err, _ := CollectChanOne(c) // Wont block worker
return err
}
Example: https://go.dev/play/p/Njwyt32B4oT
N.B. This example also have another method CollectChanRemaining() which reads all buffered elements in the channel.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论