Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use printf() in multiple threads

Tags:

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?

like image 298
user3242743 Avatar asked May 10 '14 21:05

user3242743


People also ask

Is it safe to use printf in multithreaded programs?

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.

Does C allow multi threading?

C does not contain any built-in support for multithreaded applications. Instead, it relies entirely upon the operating system to provide this feature. This tutorial assumes that you are working on Linux OS and we are going to write multi-threaded C program using POSIX.

Can multiple threads run the same function?

There is nothing wrong in calling same function from different threads. If you want to ensure that your variables are consistent it is advisable to provide thread synchronization mechanisms to prevent crashes, racearound conditions.


1 Answers

POSIX Specifications

The POSIX specification includes these functions:

  • getc_unlocked()
  • getchar_unlocked()
  • putc_unlocked()
  • putchar_unlocked()

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:

  • flockfile()
  • ftrylockfile()
  • funlockfile()

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:

  • getc()
  • getchar()
  • putc()
  • putchar()
  • fgetc()
  • fputc()

The formatted output functions are documented here:

  • printf()

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 a previous version of this answer removed as no longer relevant…

like image 183
Jonathan Leffler Avatar answered Sep 17 '22 13:09

Jonathan Leffler