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

huangapple go评论87阅读模式
英文:

Is os.File.Write() thread safe in golang?

问题

当两个Goroutine同时使用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()
}
英文:

Is it thread safe, when two Goroutines writes to file concurrently by os.File.Write()?

According to this question Is os.File's Write() threadsafe?, it isn't thread safe. However, the output file ./test.txt of the following code didn't occur errors.

And according to this question Safe to have multiple processes writing to the same file at the same time? [CentOs 6, ext4], the POSIX "raw" IO syscalls are thread safe. os.File.Write() uses the POSIX IO syscalls, so can we say it is thread safe?

package main

import (
    &quot;fmt&quot;
    &quot;os&quot;
    &quot;sync&quot;
)

func main() {
    filePath := &quot;./test.txt&quot;

    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 &lt; 100000; i++ {
            if _, err := file.Write([]byte(name + &quot;: is os.File.Write() thread safe?\n&quot;)); err != nil {
                fmt.Println(err)
            }
        }
    }

    go worker(&quot;worker1&quot;)
    go worker(&quot;worker2&quot;)

    wg.Wait()
}

答案1

得分: 9

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

不过,从 Go 1.16.5 版本的源代码来看:

// Write 实现了 io.Writer 接口。
func (fd *FD) Write(buf []byte) (int, error) {
    if err := fd.writeLock(); err != nil {
        return 0, err
    }
    defer fd.writeUnlock()
    ...

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

英文:

Documentation does not explicitly say it is thread safe.

Looking at Go 1.16.5 version source code though:

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

It uses internal synchronization. Unless you're coding a mars lander I'd say it's fine to assume writes are thread safe.

答案2

得分: 5

一般情况下,不应该期望对io.WriterWrite调用是原子性的,即一次性写入所有内容。如果你不希望输出交错,建议在更高的层次上进行同步。

即使对于*os.File,你可以假设每次Write调用都是原子性的,因为内部有锁定或者是一个单独的系统调用,但并不能保证使用文件的其他部分也是这样的。例如:

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

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

实际上,对于*os.File的写入可能是原子的,但要使其有用而不引起大量分配是困难的,而且假设这一点可能会影响你的应用程序在不同操作系统或架构,甚至不同环境中的可移植性。例如,你的编译为WASM的二进制文件可能没有相同的行为,或者当你将其写入NFS支持的文件时,行为可能会有所不同。

英文:

In general, you should not expect that Write calls to an io.Writer will be written out atomically, i.e. all at once. Synchronization at a higher level is recommended if you don't want interleaved outputs.

Even if you can assume that for *os.File each call to Write will be written out atomically because of either internal locking or because it's a single system call, there is no guarantee that whatever is using the file will do so. For example:

fmt.Fprintf(f, &quot;[%s] %s\n&quot;, date, message)

The fmt library does not guarantee that this will make a single call to the io.Writer. It may, for example, flush [, then the date, then ] then the message, and then \n separately, which could result in two log messages being interleaved.

Practically speaking, writes to *os.File will probably be atomic, but it is difficult to arrange for this to be useful without incurring significant allocations, and making this assumption might compromise the portability of your application to different operating systems or architectures, or even different environments. It is possible, for example, that your binary compiled to WASM will not have the same behavior, or that your binary when writing to an NFS-backed file will behave differently.

huangapple
  • 本文由 发表于 2021年6月25日 22:05:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/68132343.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定