英文:
How to handle asynchronous errors in Go?
问题
我正在处理我的第一个真正的Go项目,一个消息传递API。我使用通道在用户goroutine和使用线程不安全的基于事件的C协议库的库goroutine之间传递消息和其他数据。有关详细信息,请参阅https://github.com/apache/qpid-proton/blob/master/proton-c/bindings/go/README.md。
我的问题分为两个相关部分:
1. 处理通道中的错误的常见惯用法是什么?
一个端的goroutine出现问题,我如何确保另一端解除阻塞,获取一个error
值,并且以后不再被阻塞?
对于读取者:
- 我可以关闭通道,但没有错误信息。
- 我可以传递一个
struct { data, error }
。 - 或者使用第二个通道。
优缺点?其他想法?
*对于写入者:*我不能在没有panic的情况下关闭,所以我猜我需要一个第二个通道。这是惯用做法吗?
select {
case sendChan <- data: sentOk()
case err := <- errChan: oops(err)
}
我也不能在关闭后写入,所以我需要在尝试写入之前存储错误并进行检查。还有其他方法吗?
2. 在API中公开通道。
我需要通道来传递错误信息:我应该将这些通道公开为公共字段还是在方法中隐藏它们?
这里存在一个权衡,而我没有足够的经验来评估它:
-
公开通道允许用户直接选择,但它要求他们正确实现错误处理模式(在写入之前检查错误,在写入时选择错误)。这似乎很复杂且容易出错,但可能是因为我在Go方面经验不足。
-
在方法中隐藏通道简化并强制正确使用库。但现在,异步用户必须创建自己的goroutine和通道。他们可能只是重复库已经做过的事情,这很愚蠢。此外,在路径上还有一个额外的goroutine和通道。也许这不是一个大问题,但数据通道是我的库的关键路径,我认为它必须与错误通道一起隐藏。
我可以两者兼顾:为高级用户公开通道,并为简单需求的用户提供一个简单的方法包装器。这需要更多的支持,但如果单独的方法都无法适应所有情况,那么这是值得的。
标准的net.Conn使用阻塞方法而不是通道,我编写了goroutine将数据传输到我的C事件循环通道,所以我知道这是可行的,但我并不认为它是微不足道的。net.Conn在底层包装系统调用而不是通道,所以“公开通道”不是一个选项。标准库中是否有任何导出带有错误处理的通道?(time.After不算,因为没有错误)
非常感谢!
Alan
英文:
I am working on my first real Go project, a messaging API. I use channels to pass messages and other data between user goroutines and library goroutines that use a thread-unsafe, event-based C protocol library. For details <https://github.com/apache/qpid-proton/blob/master/proton-c/bindings/go/README.md>
My question is in 2 related parts:
1. What are common idioms for handling errors across channels?
The goroutine at one end blows up, how do I ensure the other end unblocks, gets an error
value and doesn't get blocked again later?
For readers:
- I can close the channel, but no error info.
- I could pass a
struct { data, error }
- or use a second channel.
Pros & cons? Other ideas?
For writers: I can't close without a panic so I guess I need a second channel. Is this idiomatic?
select {
case sendChan <- data: sentOk()
case err := <- errChan: oops(err)
}
I also can't write after close so I need to store the error somewhere and check before trying to write. Any other approaches?
2. Exposing channels in APIs.
I need channels to pass error info: should I make those channels public fields or hide them in methods?
There is a tradeoff, and I don't have the experience to evaluate it:
-
Exposing channels lets users select directly, but it requires them to correctly impement the error handling patterns (check for errors before write, select for error as well as write). This seems complex and error-prone but maybe that because I'm not seasoned in go.
-
Hiding channels in a method simplifies and enforces correct use of the library. But now an async user must create their own goroutine and channel(s). They may just duplicate what the library does already, which is silly. Also there is an extra goroutine and channel on the path. Maybe that's not a big deal, but the data channel is the critical path for my library and I think it has to be hidden along with the error channel.
I could do both: expose the channels for power users and provide a simple method wrapper for people with simple needs. That's more to support but worth it if neither alone can fit all cases.
The standard net.Conn uses blocking methods, not channels, and I wrote goroutines to pump data to my C event-loop channel so I know it can be done, but I did not find it trivial. net.Conn is wrapping sytem calls not channels underneath so "exposing the channels" is not an option. Do any of the standard libraries export channels with error handling? (time.After doesn't count, there are no errors)
Thanks a lot!
Alan
答案1
得分: 6
你的问题有点宽泛,但我会尽量根据我编写高并发代码的经验给出一些指导...
个人认为,将通道作为对象的属性,并在一个方便的NewMyObject() *MyObject
方法中进行初始化是一个很好的设计模式。这样,使用该对象的代码在每次调用类型提供的某些异步方法时就不必进行样板设置。
对于读取者:我可以关闭通道,但没有错误信息。我可以传递一个结构体{data,error}或使用第二个通道。优缺点?还有其他想法吗?
通过关闭中止通道来通知读取者返回。读取者应该简单地使用temp,err := <-FromChannel
的范式,并在数据或错误通道关闭后继续执行。这样应该可以防止工作线程出现“在关闭的通道上发送”的恐慌错误,因为它们将关闭它们的通道并返回。当err != nil
时,读取者将知道继续执行。
对于写入者:我不能关闭通道而不引发恐慌,所以我猜我需要第二个通道。这种做法是否符合惯用法?
是的。不幸的是,我对通道的单向行为感到非常恼火,认为它应该被抽象化。不管怎样,它并没有。在我的代码中,我不会在执行异步工作的对象上定义这个。我更喜欢的范式是使用关闭信号(因为在通道上发送不是一对多的,只有一个goroutine会读取它)。相反,我在调用代码中分配中止通道,如果需要关闭事物,就关闭abort
通道,所有监听该通道的执行异步工作的goroutine都会进行清理并返回。你还应该使用一个WaitGroup,这样你就可以在继续之前等待goroutine返回。
所以我的基本总结是:
1)让调用异步方法的调用者发出停止信号,而不是反过来。WaitGroup更适合用于协调它们的返回。
2)在调用代码中使用sync.WaitGroup
,以了解何时完成你的goroutine,以便你可以继续执行。
3)在调用代码中分配错误通道,并利用关闭通道产生的一对多信号;如果你在调用者中发送到一个通道,只有一个实例会从中读取。如果你在每个实例上都放置一个通道,你必须迭代实例集合以在每个实例上发送。
4)如果你有一个提供在后台执行工作的异步方法的类型,请在其初始化器中设置通道以进行读取,文档化异步方法,说明要在哪里监听数据,并提供一个非阻塞的select示例,该示例将中止通道传递给异步方法,并监听方法的数据和错误通道。如果你需要终止单个例程,你可以通过关闭它拥有的一个通道来实现,而不是通过关闭调用者的abort
通道来终止它们全部。
希望这一切都说得通。
英文:
Your question is a bit on the broad side but I'll try to give some guidance based on my experience writing highly concurrent code...
Personally I think making the channel a property of the object that gets initialized in a nice helpful NewMyObject() *MyObject
method is good design pattern. It makes it so code using the object doesn't have to do boiler plate set up every time it wants to call some asynchronous method the type offers.
For readers: I can close the channel, but no error info. I could pass a struct { data, error } or use a second channel. Pros & cons? Other ideas?
Let the reader signal to return by closing the abort channel. The reader should simply use the temp, err := <-FromChannel
paradigm and move on with execution if the data or error channel has closed. This should prevent the 'send on closed channel' panics error from the workers since they will close their channel and return. When err != nil
the reader will know to move on.
For writers: I can't close without a panic so I guess I need a second channel. Is this idiomatic?
Yes. Sadly I was quite pissed of with the uni-directional behavior of channels and though it should be abstracted. Regardless, it's not. In my code I would not define this on the object that does work asynchronously. The paradigm I prefer is to use the closing signal (since sending a on a channel is not one-to-many, only one goroutine will read that). Instead, I allocate the abort channel in the calling code and if things need to shut down you close the abort
channel and all the goroutines doing asynchronous work who are listening on that channel do their clean up and return. You should also use a WaitGroup so you can wait for the goroutines to return before moving on.
So my basic summary;
-
let the caller of asynchronous methods signal it's time to stop, not the other way around. A waitgroup is better used to coordinate their returns
-
use a
sync.WaitGroup
in the calling code to know when your goroutines are finished so you can move on -
allocate your error channel in the calling code and take advantage of the one-to-many signal produced by closing the channel; if you send on a channel you allocate in the caller, only a single instance will read from it. If you put one on each instance you have to iterate a collection of instances to send the on each.
-
if you have a type that provide async methods that do work in the background, set up the channels to read off of in it's initializer, document the async methods saying where to listen for data, provide an example of a non-blocking select that passes an abort channel into the async method and listens on the methods data and error channels. If you need to kill a single routine you could accomplish this by closing one of the channels it owns rather than killing them all by closing the callers
abort
channel.
Hopefully that all makes sense.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论