将低级调试/跟踪日志记录语句保留在关键路径中非常有用,以便可以通过运行时配置启用它们.我们的想法是,您永远不会在生产中进行此类登录(这会削弱性能),但您可以在生产环境中将其打开(例如,生产系统脱机进行调试或设置与生产系统完全相同的测试系统.)
这种类型的日志记录有一个特殊要求:在关键路径上命中禁用的日志语句的成本必须非常低:理想情况下是一个布尔测试.
在C/C++中,我会使用一个LOG宏来执行此操作,该宏在检查标志之前不会评估任何参数.只有在启用时,我们才会调用一些辅助函数来格式化和传递日志消息.
那么如何在Go中做到这一点?
将io.Discard与log.Logger一起使用是一个非启动:它每次完全格式化日志消息,如果禁用它就扔掉它.
我的第一个想法是
type EnabledLogger struct { Enabled bool; delegate *log.Logger;... }
// Implement the log.Logger interface methods as:
func (e EnabledLogger) Print(...) { if e.Enabled { e.delegate.Output(...) } }
Run Code Online (Sandbox Code Playgroud)
这很接近.如果我说:
myEnabledLogger.Printf("foo %v: %v", x, y)
Run Code Online (Sandbox Code Playgroud)
它不会被格式化或禁用,如果任何记录,但它会评估参数x和y.这对于基本类型或指针是可以的,对于任意函数调用都不行 - 例如,对没有String()方法的值进行字符串化.
我看到两种方法:
推迟调用的包装类型:
type Stringify { x *Thing }
func (s Stringify) String() { return someStringFn(s.x) }
enabledLogger.Printf("foo %v", Stringify{&aThing})
Run Code Online (Sandbox Code Playgroud)
将所有内容包装在手动启用的检查中:
if enabledLog.Enabled {
enabledLog.Printf("foo %v", someStringFn(x))
}
Run Code Online (Sandbox Code Playgroud)
两者都是冗长且容易出错的,对于某人来说,忘记一步并悄悄引入令人讨厌的表现回归太容易了. …
我正在为消息传递库编写 IO 核心,并考虑使用 libuv 与在 linux 上使用原始 epoll 和在 Windows 上使用 IOCP(最终是其他的,solaris 事件等)。我喜欢 libuv 的可移植性,我正在关注性能。
epoll 和 IOCP 允许多个线程直接等待 IO 事件,由内核进行调度。尽管我没有任何数字,但可能比用户空间调度更有效。
libuv(根据我的阅读)有一个线程安全的事件循环,但我可以实现一个领导者-跟随者线程池。我的意思是一个线程(一次)是等待事件的“领导者”。当领导者收到事件时,它发出信号,追随者应该接替领导者。前领导者处理事件,然后成为追随者。
我的希望是,假设 libuv 得到有效实现,其性能应该接近原始多线程 epoll/IOCP。我会自己测量,但我想听听有经验的人的意见。
我正在开发我的第一个真正的 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 } 优点缺点?其他想法?
对于作家:我不能不惊慌地关闭,所以我想我需要第二个渠道。这是惯用语吗?
select {
case sendChan <- data: sentOk()
case err := <- errChan: oops(err)
}
Run Code Online (Sandbox Code Playgroud)
关闭后我也无法写入,因此我需要将错误存储在某处并在尝试写入之前进行检查。还有其他方法吗?
2. 在 API 中公开渠道。
我需要通道来传递错误信息:我应该将这些通道设为公共字段还是将它们隐藏在方法中?
有一个权衡,我没有经验来评估它:
暴露通道让用户可以直接选择,但它需要他们正确执行错误处理模式(在写入之前检查错误,选择错误以及写入)。这看起来很复杂且容易出错,但也许是因为我不熟悉 Go。
在方法中隐藏通道可以简化并强制正确使用库。但是现在异步用户必须创建他们自己的 goroutine 和 channel(s)。他们可能只是复制图书馆已经做的事情,这很愚蠢。路径上还有一个额外的 goroutine 和 channel。也许这没什么大不了的,但数据通道是我的库的关键路径,我认为它必须与错误通道一起隐藏。
我可以做到这两点:为高级用户公开渠道,并为有简单需求的人提供简单的方法包装器。这更值得支持,但如果两者都不能适合所有情况,那就值得了。
标准 net.Conn 使用阻塞方法,而不是通道,我编写了 goroutines 将数据泵送到我的 C 事件循环通道,所以我知道它可以完成,但我没有发现它微不足道。net.Conn 正在包装系统调用而不是下面的通道,因此“公开通道”不是一个选项。是否有任何标准库导出带有错误处理的通道?(time.After不计算,没有错误)
非常感谢!艾伦