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 where 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);
}
pthread_join
does the following :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 callgtk_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.
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 thereforesleep(0.2)
is equivalent tosleep(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:
(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:
So, the weird outputs are because MacOS X does not implement
sem_init()
. It's odd; thesem_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 withsem_init()
). Unfortunately, the firstsem_wait()
orsem_post()
operation fails with errno = 9 (EBADF 'Bad file descriptor') again.Morals
pthread_join()
calls' behaviour.Per normal
pthread
semantics, as taught e.g. here, your original idea does seem to be confirmed: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:
a "principle of robustness" that should be used way more broadly than just in TCP/IP;-).