Concurrent Write on stdout: Thread Safety Analysis
In a recent discussion, a piece of Go code was presented that sparked a debate about thread safety when concurrently writing to stdout. The code in question is:
<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>
Thread Safety Considerations:
The question arises whether this code is thread-safe when multiple goroutines write to stdout concurrently. Various sources and opinions on the matter were mentioned but a definitive answer could not be found. Let's delve into the topic further.
fmt Package Behavior:
The fmt package functions simply take an io.Writer implementation and call Write() on it. The functions themselves are thread-safe, meaning multiple concurrent calls to fmt.F* functions are safe. However, the implementation of concurrent writing to stdout depends on the specific "writer" used.
"Writer" Implementations:
Two main categories of "writers" are relevant:
POSIX Semantics:
In case of file descriptors, POSIX requires write(2) calls to be atomic when operating on regular files or symbolic links. This means that in our case, where stdout is assumed to be a file descriptor, write calls should be atomic.
Go Standard Library Implementation:
The Go standard library's wrappers around file descriptors and sockets are designed to map write operations 1-to-1 to the underlying object. This eliminates the possibility of write calls being split or glued together.
Conclusion:
Based on the available information and the underlying semantics of the POSIX write(2) call, the provided code is not subject to data races. However, the output written to the underlying file descriptor may be intermixed in an unpredictable order. This behavior is influenced by factors such as the OS kernel version, Go version, hardware, and system load.
To ensure that the output from each specific fmt.Fprint* call appears as a contiguous piece in the resulting output, it is recommended to serialize the calls using a lock or by using the log package, which provides its own locking mechanisms.
The above is the detailed content of Is Concurrent Writing to `stdout` in Go Thread-Safe? A Detailed Analysis of `fmt.Fprintf` Behaviour.. For more information, please follow other related articles on the PHP Chinese website!