go - 在stdout线程上并发写入是否安全?

标签 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)
}
我尝试了多种长度的数据,使用了https://play.golang.org/p/29Cnwqj5K30变体
This post说不是TS。
This mail不能真正回答问题,或者我听不懂。
osfmt的软件包文档对此没有太多提及。我承认我没有挖掘这两个软件包的源代码来寻找进一步的解释,它们对我来说似乎太复杂了。
有哪些建议及其引用?

最佳答案

我不确定它是否可以作为一个明确的答案,但我将尝试提供一些见解。F*包的fmt-函数仅声明它们采用实现io.Writer接口(interface)的类型的值并在其上调用Write
这些函数本身可以安全地并发使用-从某种意义上说,可以并发调用任意数量的fmt.Fwhaveter是可以的:程序包本身已经为此做好了准备,
但是在Go中支持接口(interface)并没有说明任何关于实类型并发方面的信息。
换句话说,将允许或不允许并发的实际点推迟到fmt函数写入的“写入器”。
(还应该记住,与库存软件包fmt.*Print*所提供的功能相反,允许Write函数在其目的地多次调用log。)
因此,我们基本上有两种情况:

  • io.Writer的定制实现。
  • 它的库存实现,例如*os.Filenet包功能产生的套接字周围的包装。

  • 第一种情况很简单:无论实现者做什么。
    第二种情况更难:据我所知,Go标准库对此的立场(尽管在文档中没有明确说明)是因为它提供了围绕OS提供的“事物”(例如文件描述符和套接字)的包装是合理的。 “薄”及其所实现的任何语义都由在特定系统上运行的stdlib代码可传递地实现。
    例如,当POSIX requires that write(2) calls are atomic with regard to one another在常规文件或符号链接(symbolic link)上运行时。这意味着,由于在包装文件描述符或套接字的事物上对Write的任何调用实际上都会导致tagret系统的单个“写入”系统调用,因此您可以查阅目标操作系统的文档并了解将要发生的事情。
    请注意,POSIX仅告诉文件系统对象,并且如果os.Stdout是打开到终端(或伪终端),管道还是支持write(2) syscall的其他任何东西,则结果将取决于相关子系统和/或驱动程序实现—例如,来自多个并发调用的数据可能会散布,或者其中一个调用或两者都可能会被OS失败—不太可能,但仍然如此。
    回到Go,从我收集的数据来看,关于封装文件描述符和套接字的Go stdlib类型的以下事实适用:
  • 它们可以安全地自己并发使用(我是说,在Go级别上)。
  • 他们将WriteRead调用“一对一”映射到基础对象-也就是说,Write调用永远不会拆分为两个或多个基础syscall,并且Read调用从不从多个结果中“粘贴”底层系统调用。
    (顺便说一句,人们偶尔会被这种节俭的行为绊倒,例如,以thisthis为例。)

  • 因此,基本上,当我们考虑到fmt.*Print*可以在每次调用中自由调用Write多次的事实时,使用os.Stdout的示例将:
  • 永远不会导致数据争用-除非您为变量os.Stdout分配了一些自定义实现,否则
  • 实际写入基础FD的数据将以无法预测的顺序混合在一起,这可能取决于许多因素,包括OS内核版本和设置,用于构建程序的Go版本,硬件以及系统负载。

  • TL; DR
  • fmt.Fprint*的多个并发调用将写入同一“writer”值,从而将它们的并发性推迟到“writer”的实现(类型)。
  • 在您提出的设置中,不可能与Go stdlib提供的“文件状”对象进行数据竞争。
  • 真正的问题不在于Go程序级别的数据争用,而在于在操作系统级别上同时访问单个资源。在这里,我们通常不谈论数据争用,因为商品OS Go支持将可能“写入”的东西暴露为抽象,而真正的数据争用可能表明内核或驱动程序(以及Go的竞赛检测器将始终无法检测到它,因为该内存不会由为该进程提供动力的Go运行时所拥有。

  • 基本上,在您的情况下,如果您需要确保对fmt.Fprint*的任何特定调用所产生的数据作为单个连续片段输出到操作系统提供的实际数据接收器中,则您需要对这些调用进行序列化,因为fmt包不提供关于提供的“写入器”导出功能的Write调用次数的保证。
    序列化可以是外部的(显式的,即“获取一个锁,调用fmt.Fprint*,释放该锁”),也可以是内部的-通过将os.Stdout包装在可以管理锁的自定义类型中并使用它来进行)。
    而在使用log包时,它就可以做到这一点,并且可以直接用作它提供的“记录器”(包括默认记录器),从而禁止输出“日志头”(例如时间戳和文件名)。

    关于go - 在stdout线程上并发写入是否安全?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65214079/

    相关文章:

    go - 在 POST 请求失败时呈现模板 [golang]

    json - Golang 不能使用 as type struct array 或 slice literal

    go - 如何关闭具有多个发送者的 channel ?

    go - 接口(interface)在 Go 中究竟是如何工作的?

    javascript - 在 V8 中集成 seb API(setTimeout、fetch 等)的最佳方式是什么

    go - 接口(interface)作为结构中的字段,接口(interface)调用将输入作为相同结构的方法

    Go ReadString 问题 : The filename, 目录名,或卷标语法不正确

    golang并发同步问题

    go - 字符串到 char (*array)[]

    使用接口(interface)和动态类型进行变量分配