How to use printf() in multiple threads

user3242743 picture user3242743 · May 10, 2014 · Viewed 28.7k times · Source

I am implementing a multithreaded program that uses different cores, and many threads are executed simultaneously. Each thread makes a printf() call, and the result is not readable.

How can I make printf() atomic, so that a printf() call in one thread doesn't conflict with a printf() call in another?

Answer

Jonathan Leffler picture Jonathan Leffler · Oct 22, 2016

POSIX Specifications

The POSIX specification includes these functions:

Versions of the functions getc(), getchar(), putc(), and putchar() respectively named getc_unlocked(), getchar_unlocked(), putc_unlocked(), and putchar_unlocked() shall be provided which are functionally equivalent to the original versions, with the exception that they are not required to be implemented in a fully thread-safe manner. They shall be thread-safe when used within a scope protected by flockfile() (or ftrylockfile()) and funlockfile(). These functions can safely be used in a multi-threaded program if and only if they are called while the invoking thread owns the (FILE *) object, as is the case after a successful call to the flockfile() or ftrylockfile() functions.

The specification for these functions mention:

The specification for flockfile() et al includes the blanket requirement:

All functions that reference (FILE *) objects, except those with names ending in _unlocked, shall behave as if they use flockfile() and funlockfile() internally to obtain ownership of these (FILE *) objects.

This supersedes the suggested code in previous editions of this answer. The POSIX standard also specifies:

The [*lockfile()] functions shall behave as if there is a lock count associated with each (FILE *) object. This count is implicitly initialized to zero when the (FILE *) object is created. The (FILE *) object is unlocked when the count is zero. When the count is positive, a single thread owns the (FILE *) object. When the flockfile() function is called, if the count is zero or if the count is positive and the caller owns the (FILE *) object, the count shall be incremented. Otherwise, the calling thread shall be suspended, waiting for the count to return to zero. Each call to funlockfile() shall decrement the count. This allows matching calls to flockfile() (or successful calls to ftrylockfile()) and funlockfile() to be nested.

There are also the specifications for the character I/O functions:

The formatted output functions are documented here:

One key provision in the printf() specification is:

Characters generated by fprintf() and printf() are printed as if fputc() had been called.

Note the use of 'as if'. However, each of the printf() functions is required to apply the lock so that access to a stream is controlled in a multi-threaded application. Only one thread at a time can be using a given file stream. If the operations are user-level calls to fputc(), then other threads can intersperse the output. If the operations are user-level calls such as printf(), then the whole call and all access to the file stream is effectively protected so that only one thread is using it until the call to printf() returns.

In the section of the System Interfaces: General Information section of POSIX on the subject of Threads, it says:

2.9.1 Thread-Safety

All functions defined by this volume of POSIX.1-2008 shall be thread-safe, except that the following functions1 need not be thread-safe.

…a list of functions that need not be thread-safe…

… The getc_unlocked(), getchar_unlocked(), putc_unlocked(), and putchar_unlocked() functions need not be thread-safe unless the invoking thread owns the (FILE *) object accessed by the call, as is the case after a successful call to the flockfile() or ftrylockfile() functions.

Implementations shall provide internal synchronization as necessary in order to satisfy this requirement.

The list of exempted functions does not contain fputc or putc or putchar (or printf() et al).

Interpretation

Rewritten 2017-07-26.

  1. Character-level output on a stream is thread-safe unless using the 'unlocked' functions without first locking the file.
  2. Higher-level functions such as printf() conceptually call flockfile() at the start an funlockfile() at the end, which means that the POSIX-defined stream output functions are also thread-safe per call.
  3. If you wish to group operations on a file stream for a single thread, you can do so by explicitly using calls to flockfile() and funlockfile() on the relevant stream (without interfering with the system's use of the *lockfile() functions.

This means there is no need to create mutexes or equivalent mechanisms for yourself; the implementation provides the functions to allow you to control the access to printf() et al in a multi-threaded application.

…Code from previous answer removed as no longer relevant…