Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

weird output when I use pthread and printf

Tags:

c

printf

pthreads

I write a program using pthread.

Environment:windows 7 , CYGWIN_NT-6.1 i686 Cygwin , gcc (GCC) 4.5.3

The source code

#include<stdio.h>
#include<pthread.h>

void *th_func(void *p)
{
    int iLoop = 0;

    for(iLoop = 0;iLoop<100;iLoop++)
    {
        printf("Thread Thread Thread Thread\n");
    }

    return;
}

int main()
{
    int iLoop = 0;
    pthread_t QueThread;

    printf("Main : Start Main\n");

    printf("Main : Start Create Thread\n");
    pthread_create(&QueThread,NULL,th_func,NULL);
    printf("Main : End Create Thread\n");

    for(iLoop = 0;iLoop<100;iLoop++)
    {
        printf("Main Main Main Main\n");
    }

    pthread_join(QueThread,NULL);

    printf("Main : End Main\n");

    printf("---------------\n");

    return 0;
}

When I compile the source code, there are no warnings or errors,but it's output is weird.

A part of it's output

Main : Start Main
Main : Start Create Thread
Thread Thread Thread ThreThread Thread Thread Thread
Main Main Main Main
Thread Thread Thread Thread
Main Main Main Main

I want to know the cause of such phenomenon.

In this output, Main : End Create Thread is not printed completely. And at line 3, a newline \n at the end of "Thread Thread Thread Thread\n" disappear.

Is everyone's output like this? It does not occur every time, but occurs sometime.

If I use mutex to call printf safely,the weird output seem to be stopped.

POSIX says printf is thread-safe, and according to Cygwin.com, cygwin provides posix-style API. However, there is the unexpected output.

Is printf really thread-safe?

I executed the same program 100 times in Linux(Ubuntu), and this output did not occur.

In addition, I have not understood the reason why some words on the output disappeared.

like image 838
Takashi Ikejima Avatar asked Nov 02 '12 06:11

Takashi Ikejima


2 Answers

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.

like image 198
Jonathan Leffler Avatar answered Nov 09 '22 16:11

Jonathan Leffler


This looks like it may be a bug in Cygwin, or maybe something is misconfigured. Several answer here indicate that 'thread safe' only promises that the function won't cause harm to the program, and that thread safety doesn't necessarily mean that a function is 'atomic'. But, as far as I know, POSIX doesn't formally define 'thread safe' (if anyone has a pointer to such a definition, please post it in a comment).

However, not only does POSIX specify that printf() is thread safe, POSIX also specifies that:

All functions that reference ( FILE *) objects shall behave as if they use flockfile() and funlockfile() internally to obtain ownership of these ( FILE *) objects.

Since printf() implicitly references the stdout FILE* object, all printf() calls should be atomic with respect to each other (and any other function that uses stdout).

Note that this might not be true on other systems, but in my experience it does hold true for many multi threaded systems.

like image 29
Michael Burr Avatar answered Nov 09 '22 17:11

Michael Burr