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 (
"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()
}
2
Answers
Documentation does not explicitly say it is thread safe.
Looking at Go 1.16.5 version source code though:
It uses internal synchronization. Unless you’re coding a mars lander I’d say it’s fine to assume writes are thread safe.
In general, you should not expect that
Write
calls to anio.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 toWrite
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:The
fmt
library does not guarantee that this will make a single call to theio.Writer
. It may, for example, flush[
, then the date, then]
then the message, and thenn
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.