How to use printf() in multiple threads
Asked Answered
D

3

29

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?

Draghound answered 10/5, 2014 at 21:47 Comment(6)
not sure about what you want to say. But How can i do it without using a libraray, i mean like using semaphores..etcDraghound
How about creating a printf wrapping function and using a mutex?Nikolenikoletta
Define "conflict". printf is "atomic" in the sense that you mean but it can't stop multiple threads writing to the same output destination.Palaearctic
@Palaearctic : No sure that what you are saying is true. I thought that the buffer used by printf can be affected by other threadsDraghound
Show a code example that illustrates the problem. Like @Duck, I've never seen two printf writes intermingled.Howlond
FWIW I've seen a logging helper function do the equivalent of: printf( "xyz [%s]: ", level ); printf( fmt, args ); - this leads to multi-threading issues that need explicit locking to fix.Adah
U
28

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

Upperclassman answered 21/10, 2016 at 22:7 Comment(5)
"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." Citation needed. I don't see that requirement in the standard language.Eventful
Did you look in the flockfile() specification linked in the answer? I believe it says so quite explicitly, as quoted in the answer — All functions that reference FILE * objects … shall behave as if they use flockfile() and funlockfile() internally to obtain ownership of these (FILE *) objects. I grant you that's a reasonably well hidden location for such a fundamental requirement, but that's where the POSIX standard says "user-level calls such as printf() [are] effectively protected".Upperclassman
It only says that ownership needs to be obtained, not that it needs to be held continuously throught the call. It looks to me like a printf implementation that dropped the lock and reacquired it occasionally would still adhere to the letter of the requirement. As long as it held the lock over actual output it would still preserve some level of consistency for the underlying objects, but it wouldn't prevent interleaving.Eventful
I don't think the standards committee was expecting implementers to be so…perverse? devious?…as to think of not holding the lock on the file stream throughout the call. You could try making an implementation that behaved as you suggest, but I think you'd quickly find that the QoI (quality of implementation) was regarded as "unsatisfactory" by users, who would cease using it. The intention is fairly clear that the code should lock the stream on entry (or, at least, before first use of the stream itself) and unlock it before return (or, at least, after the last use of the stream itself).Upperclassman
Fair enough! I think it's the job of standards committees to write perversion-proof language, and it wouldn't be that hard in this case. But I'm not coming up with any good reason why someone would need to drop the lock temporarily mid-print, so I'm sure you're correct that nobody does that....Eventful
V
21

In order not to mix the outputs from different threads, you need to make sure that only one thread uses printf at a time. To achieve this, the simplest solution is to use a mutex. At the beginning initialize the mutex :

static pthread_mutex_t printf_mutex;
...
int main()
{
    ...
    pthread_mutex_init(&printf_mutex, NULL);
    ...

Then make a wrapper around printf to make sure that only the thread that got the mutex can call printf (otherwise it will have to block until the mutex is available) :

int sync_printf(const char *format, ...)
{
    va_list args;
    va_start(args, format);

    pthread_mutex_lock(&printf_mutex);
    vprintf(format, args);
    pthread_mutex_unlock(&printf_mutex);

    va_end(args);
}
Voletta answered 10/5, 2014 at 23:10 Comment(2)
Another possibility is to use a service thread whose job is to interact with the terminal. Other threads can queue "print jobs" to it.Owlet
There should be no need to define your own lock, as there are already standard functions for this; see e.g. gnu.org/software/libc/manual/html_node/Streams-and-Threads.html (which also claims that printf on its own is guaranteed to be atomic. I don't know if that's correct.)Eventful
R
-3

For linux ,here's the code for u in c:3 threads ,executing on different cores printing hello world ,not conflicting with each other courtisey of lock .

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <syscall.h>
#include <sys/types.h>

void * printA ( void *);
void * printB ( void *);
void * printC ( void *);

pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;


int main(int argc,  char *argv[]) {
   int error;
   pthread_t tid1, tid2,tid3;

    if ( error = pthread_create (&tid1, NULL, printA, NULL ))
        {
        fprintf (stderr, "Failed to create first thread: %s\n",strerror(error));
        return 1;
    }
    if ( error = pthread_create (&tid2, NULL, printB, NULL ))
        {
        fprintf (stderr, "Failed to create second thread: %s\n",strerror(error));
        return 1;
    }
    if ( error = pthread_create (&tid3, NULL, printC, NULL ))
        {
        fprintf (stderr, "Failed to create second thread: %s\n",strerror(error));
        return 1;
    }

    if (error = pthread_join(tid1, NULL))
        {
        fprintf (stderr, "Failed to join first thread: %s\n",strerror(error));
        return 1;
    }
    if (error = pthread_join(tid2, NULL))
        {
        fprintf (stderr, "Failed to join second thread: %s\n",strerror(error));
        return 1;
    }

    if (error = pthread_join(tid3, NULL))
        {
        fprintf (stderr, "Failed to join second thread: %s\n",strerror(error));
        return 1;
    }
    return 0;
}

void * printA ( void *arg )
{
      if ( error = pthread_mutex_lock( &mylock ))
      {
    fprintf (stderr, "Failed to acquire lock in printA: %s\n",strerror(error));
    return NULL;
      }
   printf("Hello world\n");

      if ( error = pthread_mutex_unlock( &mylock ))
      {
    fprintf (stderr, "Failed to release lock in printA: %s\n",strerror(error));
    return NULL;
      }
   }

void * printB ( void *arg )
{
   int error;
      if ( error = pthread_mutex_lock( &mylock ))
      {
    fprintf (stderr, "Failed to acquire lock in printB: %s\n",strerror(error));
    return NULL;
      }


   printf("Hello world\n");

      if ( error = pthread_mutex_unlock( &mylock ))
      {
    fprintf (stderr, "Failed to release lock in printA: %s\n",strerror(error));
    return NULL;
      }
   }


void * printC ( void *arg )
{
   int error;
      if ( error = pthread_mutex_lock( &mylock ))
      {
    fprintf (stderr, "Failed to acquire lock in printB: %s\n",strerror(error));
    return NULL;
      }


   printf("Hello world\n");

      if ( error = pthread_mutex_unlock( &mylock ))
      {
    fprintf (stderr, "Failed to release lock in printA: %s\n",strerror(error));
    return NULL;
      }
   }
R answered 10/5, 2014 at 23:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.