stdout 上的并发写入:线程安全分析
在最近的一次讨论中,出现了一段 Go 代码,引发了关于线程的争论同时写入标准输出时的安全性。有问题的代码是:
<code class="go">package main import ( "fmt" "os" "strings" ) func main() { x := strings.Repeat(" ", 1024) go func() { for { fmt.Fprintf(os.Stdout, x+"aa\n") } }() go func() { for { fmt.Fprintf(os.Stdout, x+"bb\n") } }() go func() { for { fmt.Fprintf(os.Stdout, x+"cc\n") } }() go func() { for { fmt.Fprintf(os.Stdout, x+"dd\n") } }() <-make(chan bool) }</code>
线程安全注意事项:
当多个 goroutine 并发写入 stdout 时,会出现此代码是否是线程安全的问题。提到了有关此事的各种消息来源和意见,但未能找到明确的答案。让我们进一步深入研究这个主题。
fmt 包行为:
fmt 包函数只需采用 io.Writer 实现并对其调用 Write() 即可。这些函数本身是线程安全的,这意味着对 fmt.F* 函数的多个并发调用是安全的。然而,并发写入 stdout 的实现取决于所使用的具体“writer”。
“Writer”实现:
“writers”的两个主要类别是相关的:
POSIX 语义:
对于文件描述符,POSIX 需要 write (2) 在操作常规文件或符号链接时调用原子性。这意味着在我们的例子中,假设 stdout 是文件描述符,写入调用应该是原子的。
Go 标准库实现:
Go 标准库的文件描述符和套接字的包装器旨在将写入操作一对一映射到底层对象。这消除了 write 调用被拆分或粘合在一起的可能性。
结论:
基于 POSIX write(2) 调用的可用信息和底层语义,所提供的代码不受数据竞争的影响。然而,写入底层文件描述符的输出可能会以不可预测的顺序混合。此行为受到操作系统内核版本、Go 版本、硬件和系统负载等因素的影响。
确保每个特定 fmt.Fprint* 调用的输出在结果输出中显示为连续的片段,建议使用锁或使用日志包来序列化调用,日志包提供了自己的锁定机制。
以上是Go 中并发写入“stdout”是线程安全的吗? `fmt.Fprintf` 行为的详细分析。的详细内容。更多信息请关注PHP中文网其他相关文章!