The POSIX standard has functions like putc_unlocked()
where the commentary says:
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 thread-safe manner. They may only safely be used within a scope protected by flockfile()
(or ftrylockfile()
) and funlockfile()
. These functions may 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.
That clearly indicates that the low-level functions for single character I/O are normally thread-safe. However, it also indicates that the level of granularity is a single character output operation. The specification for printf()
says:
Characters generated by fprintf()
and printf()
are printed as if fputc()
had been called.
And for putc()
, it says:
The putc()
function shall be equivalent to fputc()
, except that if it is implemented as a macro it may evaluate stream more than once, so the argument should never be an expression with side-effects.
The page for fputc()
doesn't say anything about thread-safety, so you have to look elsewhere for that information.
Another section describes threads and says:
All functions defined by this volume of POSIX.1-2008 shall be thread-safe, except that the following functions need not be thread-safe.
And the list following includes the *_unlocked()
functions.
So, printf()
and fputc()
have to be thread-safe, but the writing by printf()
is done 'as if' by fputc()
, so the interleaving of output between threads may be at the character level, which is more or less consistent with what you see. If you want to make calls to printf()
non-interleaved, you would need to use the flockfile()
and funlockfile()
calls to give your thread ownership of stdout
while the printf()
is executed. Similarly for fprintf()
. You could write an fprintf_locked()
function quite easily to achieve this result:
int fprintf_locked(FILE *fp, const char *format, ...)
{
flockfile(fp);
va_list args;
va_start(args, format);
int rc = vfprintf(fp, format, args);
va_end(args);
funlockfile(fp);
return rc;
}
You could insert a fflush(fp)
in there if you wished. You could also have a vfprintf_locked()
and have the function above call that to do the lock, format, (flush) and unlock operations. It's probably how I'd code it, trusting the compiler to inline the code if that was appropriate and doable. Supporting the versions using stdout
is likewise pretty straight-forward.
Note the fragment of POSIX specification for flockfile()
quoted by Michael Burr in his answer:
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.
Apart from the odd parentheses around the FILE *
, these lines impact all the other standard I/O functions, but you have to know that these lines exist in one of the less frequently used man pages. Thus, my fprintf_locked()
function should be unnecessary. If you find an aberrant implementation of fprintf()
that does not lock the file, then the fprintf_locked()
function could be used instead, but it should only be done under protest — the library should be doing that for you anyway.