tags:

views:

356

answers:

7

For some reason I thought that calling pthread_exit(NULL) at the end of a main function would guarantee that all running threads (at least created in the main function) would finish running before main could exit. However when I run this code below without calling the two pthread_join functions (at the end of main) explicitly I get a segmentation fault, which seems to happen because the main function has been exited before the two threads finish their job, and therefore the char buffer is not available anymore. However when I include these two pthread_join function calls at the end of main it runs as it should. To guarantee that main will not exit before all running threads have finished, is it necessary to call pthread_join explicitly for all threads initialized directly in main?

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include <semaphore.h>
#define NUM_CHAR 1024
#define BUFFER_SIZE 8

typedef struct {
    pthread_mutex_t mutex; 
    sem_t full;
    sem_t empty;
    char* buffer;
} Context;

void *Reader(void* arg) {
    Context* context = (Context*) arg;
    for (int i = 0; i < NUM_CHAR; ++i) {
        sem_wait(&context->full);
        pthread_mutex_lock(&(context->mutex));
        char c = context->buffer[i % BUFFER_SIZE];
        pthread_mutex_unlock(&(context->mutex));
        sem_post(&context->empty);

        printf("%c", c);
    }
    printf("\n");
    return NULL;
}

void *Writer(void* arg) {
    Context* context = (Context*) arg;
    for (int i = 0; i < NUM_CHAR; ++i) {
        sem_wait(&context->empty);
        pthread_mutex_lock(&(context->mutex));
        context->buffer[i % BUFFER_SIZE] = 'a' + (rand() % 26);
        float ranFloat = (float) rand() / RAND_MAX;
        if (ranFloat < 0.5) sleep(0.2);
        pthread_mutex_unlock(&(context->mutex));
        sem_post(&context->full);
    }
    return NULL;
}

int main() {
    char buffer[BUFFER_SIZE];
    pthread_t reader, writer;
    Context context;
    srand(time(NULL));
    int status = 0;
    status = pthread_mutex_init(&context.mutex, NULL);
    status = sem_init(&context.full,0,0);
    status = sem_init(&context.empty,0, BUFFER_SIZE);
    context.buffer = buffer;

    status = pthread_create(&reader, NULL, Reader, &context);
    status = pthread_create(&writer, NULL, Writer, &context);

    pthread_join(reader,NULL);   // This line seems to be necessary
    pthread_join(writer,NULL);   // This line seems to be necessary

    pthread_exit(NULL);
    return 0;
}

If that is the case, how could I handle the case were plenty of identical threads (like in the code below) would be created using the same thread identifier? In that case, how can I make sure that all the threads will have finished before main exits? Do I really have to keep an array of NUM_STUDENTS pthread_t identifiers to be able to do this? I guess I could do this by letting the Student threads signal a semaphore and then let the main function wait on that semaphore, but is there really no easier way to do this?

int main()
{
    pthread_t thread;
    for (int i = 0; i < NUM_STUDENTS; i++)
        pthread_create(&thread,NULL,Student,NULL);  // Threads 
    // Make sure that all student threads have finished
    exit(0);
}
A: 

pthread_join() is the standard way to wait for the other thread to complete, I would stick to that.

Alternatively, you can create a thread counter and have all child threads increment it by 1 at start, then decrement it by 1 when they finish (with proper locking of course), then have your main() wait for this counter to hit 0. (pthread_cond_wait() would be my choice).

m1tk4
+5  A: 

pthread_exit() is a function called by a thread to terminate its own execution. For the situation you've given it is not to be called from your main program thread.

As you have figured out, pthread_join() is the correct means to wait for the completion of a joinable thread from main().

Also as you've figured out, you need to maintain the value returned from pthread_create() to pass to pthread_join().

What this means is that you cannot use the same pthread_t variable for all the threads you create if you intend to use pthread_join().

Rather, build an array of pthread_t so that you have a copy of each thread's ID.

Amardeep
Can you quote an authority for your statement '[i]t is never to be called from your main program thread'?
Jonathan Leffler
@Jonathan: It is certainly not authoritative. Rather it is just advice for someone new to pthreads. Perhaps my language comes across the wrong way. I'll adjust it. Thank you for pointing that out.
Amardeep
A: 

pthread_join does the following :

The pthread_join() function suspends execution of the calling thread until the target thread terminates, unless the target thread has already terminated. On return from a successful pthread_join() call with a non-NULL value_ptr argument, the value passed to pthread_exit() by the terminating thread is made available in the location referenced by value_ptr. When a pthread_join() returns successfully, the target thread has been terminated. The results of multiple simultaneous calls to pthread_join() specifying the same target thread are undefined. If the thread calling pthread_join() is canceled, then the target thread will not be detached.

However you can achieve the same by using a light weight loop which will prevent the exe from exiting. In Glib this is achieved by creating a GMainLoop, in Gtk+ you can use the gtk_main. After completion of threads you have to quit the main loop or call gtk_exit.

Alternatively you can create you own wait functionality using a combination of sockets,pipes and select system call but this is not required and can be considered as an exercise for practice.

Praveen S
A: 

Per normal pthread semantics, as taught e.g. here, your original idea does seem to be confirmed:

If main() finishes before the threads it has created, and exits with pthread_exit(), the other threads will continue to execute. Otherwise, they will be automatically terminated when main() finishes.

However I'm not sure whether that's part of the POSIX threads standard or just a common but not universal "nice to have" add-on tidbit (I do know that some implementations don't respect this constraint -- I just don't know whether those implementations are nevertheless to be considered standard compliant!-). So I'll have to join the prudent chorus recommending the joining of every thread you need to terminate, just to be on the safe side -- or, as Jon Postel put it in the context of TCP/IP implementations:

Be conservative in what you send; be liberal in what you accept.

a "principle of robustness" that should be used way more broadly than just in TCP/IP;-).

Alex Martelli
The problem here isn't that the other threads aren't continuing to execute - they are - it's that the data they need has been destroyed, since it was declared as `auto` variables in `main()`.
caf
A: 

pthread_exit(3) exits the thread that calls it (but not the whole process if other threads are still running). In your example other threads use variables on main's stack, thus when main's thread exits and its stack is destroyed they access unmapped memory, thus the segfault.

Use proper pthread_join(3) technique as suggested by others, or move shared variables into static storage.

Nikolai N Fetissov
+2  A: 

Quite aside from whether the program should or should not terminate when the main thread calls pthread_exit, pthread_exit says

The pthread_exit() function terminates the calling thread

And also:

After a thread has terminated, the result of access to local (auto) variables of the thread is undefined.

Since the context is an automatic variable of main(), your code can fall over before it even gets to the point of testing what you want it to test...

Steve Jessop
Exactly. The OP could fix the problem by declaring `Context` and `buffer` with static storage duration.
caf
A: 

A mini saga

You don't mention the environment in which you are running the original code. I modified your code to use nanosleep() (since, as I mentioned in a comment to the question, sleep() takes an integer and therefore sleep(0.2) is equivalent to sleep(0)), and compiled the program on MacOS X 10.6.4.

Without error checking

It works fine; it took about 100 seconds to run with the 0.5 probability factor (as you'd expect; I changed that to 0.05 to reduce the runtime to about 10 seconds), and generated a random string - some of the time.

Sometimes I got nothing, sometimes I got more and sometimes I got less data. But I didn't see a core dump (not even with 'ulimit -c unlimited' to allow arbitrarily large core dumps).

Eventually, I applied some tools and got to see that I always got 1025 characters (1024 generated plus a newline), but quite often, I got 1024 ASCII NUL characters. Sometimes they'd appear in the middle, sometimes at the beginning, etc:

$  ./pth | tpipe -s "vis | ww -w64" "wc -c"
    1025
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000ocriexffwgdvdvyfitjtvlzcoffhusjo
zyacniffpsfswesgrkuxycsubufamxxzkrkqnwvsxcbmktodessyohixsmuhdovt
hhertqjjinzoptcuqzertybicrzaeyqlyublbfgutcdvftwkuvxhouiuduoqrftw
xjkgqutpryelzuaerpsbotwyskaflwofseibfqntecyseufqxvzikcyeeikjzsye
qxhjwrjmunntjwhohqovpwcktolcwrvmfvdfsmkvkrptjvslivbfjqpwgvroafzn
fkjumqxjbarelbrdijfrjbtiwnajeqgnobjbksulvcobjkzwwifpvpmpwyzpwiyi
cdpwalenxmocmtdluzouqemmjdktjtvfqwbityzmronwvulfizpizkiuzapftxay
obwsfajcicvcrrjehjeyzsngrwusbejiovaaatyzouktetcerqxjsdpswixjpege
blxscdebfsptxwvwsllvydipovzmnrvoiopmqotydqaujwdykidmwzitdsropguv
vudyfiaaaqueyllnwudfpplcfbsngqqeyucdawqxqzczuwsnaquofreilzvdwbjq
ksrouwltvaktpdrvjnqahpdqdshmmvntspglexggshqbjrvxceaqlfnukedxzlms
cnapdtgtcoyhnglojbjnplowericrzbfulvrobfn
$

(The 'tpipe' program is like 'tee' but it writes to pipes instead of files (and to standard output unless you specify the '-s' option); 'vis' comes from 'The UNIX Programming Environment' by Kernighan & Pike; 'ww' is a 'word wrapper' but there aren't any words here so it brute force wraps at width 64.)

The behaviour I was seeing was highly indeterminate - I'd get different results on each run. I even replaced the random characters with the alphabet in sequence ('a' + i % 26), and was still getting odd behaviour.

I added some debug printing code (and a counter to the contex), and it was clear that the semaphore context->full was not working properly for the reader - it was being allowed to go into the mutual exclusion before the writer had written anything.

With error checking

When I added error checking to the mutex and semaphore operations, I found that:

sem_init(&context.full) failed (-1)
errno = 78 (Function not implemented)

So, the weird outputs are because MacOS X does not implement sem_init(). It's odd; the sem_wait() function failed with errno = 9 (EBADF 'Bad file descriptor'); I added the checks there first. Then I checked the initialization...

Using sem_open() instead of sem_init()

The sem_open() calls succeed, which looks good (names "/full.sem" and "/empty.sem", flags O_CREAT, mode values of 0444, 0600, 0700 at different times, and initial values 0 and BUFFER_SIZE, as with sem_init()). Unfortunately, the first sem_wait() or sem_post() operation fails with errno = 9 (EBADF 'Bad file descriptor') again.

Morals

  • It is important to check error conditions from system calls.
  • The output I see is non-deterministic because the semaphores don't work.
  • That doesn't alter the 'it does not crash without the pthread_join() calls' behaviour.
  • MacOS X does not have a working POSIX semaphore implementation.
Jonathan Leffler
Generally, the `sem_t` functions may be interrupted by signal handlers (e.g I/O) and it may have `EINTR` in `errno` in that case. So yes, you should capture the error of *all* `sem_t` functions, regardless which interface you chose to create them.
Jens Gustedt