golang 中的 os.File.Write() 线程安全吗?

Posted

技术标签:

【中文标题】golang 中的 os.File.Write() 线程安全吗?【英文标题】:Is os.File.Write() thread safe in golang? 【发布时间】:2021-09-08 22:15:57 【问题描述】:

当两个 Goroutines 通过 os.File.Write() 同时写入文件时,它是线程安全的吗?

根据这个问题Is os.File's Write() threadsafe?,它不是线程安全的。但是下面代码的输出文件./test.txt并没有出现错误。

根据这个问题Safe to have multiple processes writing to the same file at the same time? [CentOs 6, ext4],POSIX“原始”IO 系统调用是线程安全的。 os.File.Write() 使用 POSIX IO 系统调用,所以我们可以说它是线程安全的吗?

package main

import (
    "fmt"
    "os"
    "sync"
)

func main() 
    filePath := "./test.txt"

    var wg sync.WaitGroup
    wg.Add(2)

    worker := func(name string) 
        // file, _ := os.Create(filePath)
        file, _ := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE, 0666)
        defer file.Close()
        defer wg.Done()
        for i := 0; i < 100000; i++ 
            if _, err := file.Write([]byte(name + ": is os.File.Write() thread safe?\n")); err != nil 
                fmt.Println(err)
            
        
    

    go worker("worker1")
    go worker("worker2")

    wg.Wait()

【问题讨论】:

是的,它们与您指出的底层系统调用一样安全,但如果文件未处于附加模式,则这样做没有任何意义。此外,os.Create 的文档特别声明了If the file already exists, it is truncated See also. 哦,我用错了os.Create()。我应该用os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0666) 替换它吗? @JimB 并发写入文件的唯一方法是appending,这至少可以保证写入不会交错(在 POSIX 上,我不熟悉视窗)。考虑尝试写入任何流,例如 TCP 连接;如果数据可以在多个写入者之间随机混合,那么同时写入有什么好处?仅仅因为它是安全的,并不意味着这样做是合乎逻辑的。 系统不知道语法句是什么,所以不能防止它们混在一起。在 POSIX 上使用 O_APPEND 时,保证不会交叉写入单个写入。除此之外,您的结果可能会有所不同。如果你想要更高级别的协调,你必须提供更高级别的协调。 【参考方案1】:

文档没有明确说明它是线程安全的。

查看 Go 1.16.5 版本源代码:

// Write implements io.Writer.
func (fd *FD) Write(buf []byte) (int, error) 
    if err := fd.writeLock(); err != nil 
        return 0, err
    
    defer fd.writeUnlock()
    ...

它使用内部同步。除非您正在编写火星着陆器,否则我认为可以假设写入是线程安全的。

【讨论】:

【参考方案2】:

通常,您不应期望 Writeio.Writer 的调用会以原子方式写入,即一次全部写入。如果您不想要交错输出,建议在更高级别进行同步。

即使您可以假设对于*os.File,每个对Write 的调用都将由于内部锁定或因为它是单个系统调用而被原子地写出,但也不能保证使用的任何东西 em> 文件会这样做。例如:

fmt.Fprintf(f, "[%s] %s\n", date, message)

fmt 库不保证这将对io.Writer 进行一次调用。例如,它可能会分别刷新[,然后是日期,然后是] ,然后是消息,最后是\n,这可能会导致两条日志消息交错。

实际上,对*os.File 的写入可能是原子性的,但很难在不产生大量分配的情况下使其有用,并且做出这种假设可能会损害您的应用程序对不同操作系统或架构的可移植性,甚至不同的环境。例如,您编译为 WASM 的二进制文件可能不会有相同的行为,或者您的二进制文件在写入 NFS 支持的文件时会有不同的行为。

【讨论】:

以上是关于golang 中的 os.File.Write() 线程安全吗?的主要内容,如果未能解决你的问题,请参考以下文章

Golang入门到项目实战 golang中的if语句

golang Golang中的字符串连接

golang Golang中的简单队列实现

golang Golang中的Http Redirect

golang Golang中的简单HTTP API

golang Golang中的信号量示例实现