Reputation: 141
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);
}
Upvotes: 14
Views: 45152
Reputation: 10393
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 successfulpthread_join()
call with a non-NULLvalue_ptr
argument, the value passed topthread_exit()
by the terminating thread is made available in the location referenced byvalue_ptr
. When apthread_join()
returns successfully, the target thread has been terminated. The results of multiple simultaneous calls topthread_join()
specifying the same target thread are undefined. If the thread callingpthread_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.
Upvotes: 0
Reputation: 3467
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).
Upvotes: 1
Reputation: 19034
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.
Upvotes: 18
Reputation: 61
There is no need for calling pthread_join(reader,NULL);
at all if Context
and buffer
are declared with static storage duration (as already pointed out by Steve Jessop, caf and David Schwartz).
Declaring Context
and buffer
static also makes it necessary to change Context *context
to Context *contextr
or Context *contextw
respectively.
In addition, the following rewrite called pthread_exit.c
replaces sem_init()
with sem_open()
and uses nanosleep()
(as suggested by Jonathan Leffler).
pthread_exit
was tested on Mac OS X 10.6.8 and did not output any ASCII NUL characters.
/*
cat pthread_exit.c (sample code to test pthread_exit() in main())
source:
"pthreads in C - pthread_exit",
http://stackoverflow.com/questions/3330048/pthreads-in-c-pthread-exit
compiled on Mac OS X 10.6.8 with:
gcc -ansi -pedantic -std=gnu99 -Os -Wall -Wextra -Wshadow -Wpointer-arith -Wcast-qual -Wstrict-prototypes \
-Wmissing-prototypes -Wformat=2 -l pthread -o pthread_exit pthread_exit.c
test with:
time -p bash -c './pthread_exit | tee >(od -c 1>&2) | wc -c'
*/
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include <semaphore.h>
#include <time.h>
void *Reader(void* arg);
void *Writer(void* arg);
// #define NUM_CHAR 1024
#define NUM_CHAR 100
#define BUFFER_SIZE 8
typedef struct {
pthread_mutex_t mutex;
sem_t *full;
sem_t *empty;
const char *semname1;
const char *semname2;
char* buffer;
} Context;
static char buffer[BUFFER_SIZE];
static Context context;
void *Reader(void* arg) {
Context *contextr = (Context*) arg;
for (int i = 0; i < NUM_CHAR; ++i) {
sem_wait(contextr->full);
pthread_mutex_lock(&(contextr->mutex));
char c = contextr->buffer[i % BUFFER_SIZE];
pthread_mutex_unlock(&(contextr->mutex));
sem_post(contextr->empty);
printf("%c", c);
}
printf("\n");
return NULL;
}
void *Writer(void* arg) {
Context *contextw = (Context*) arg;
for (int i = 0; i < NUM_CHAR; ++i) {
sem_wait(contextw->empty);
pthread_mutex_lock(&(contextw->mutex));
contextw->buffer[i % BUFFER_SIZE] = 'a' + (rand() % 26);
float ranFloat = (float) rand() / RAND_MAX;
//if (ranFloat < 0.5) sleep(0.2);
if (ranFloat < 0.5)
nanosleep((struct timespec[]){{0, 200000000L}}, NULL);
pthread_mutex_unlock(&(contextw->mutex));
sem_post(contextw->full);
}
return NULL;
}
int main(void) {
pthread_t reader, writer;
srand(time(NULL));
int status = 0;
status = pthread_mutex_init(&context.mutex, NULL);
context.semname1 = "Semaphore1";
context.semname2 = "Semaphore2";
context.full = sem_open(context.semname1, O_CREAT, 0777, 0);
if (context.full == SEM_FAILED)
{
fprintf(stderr, "%s\n", "ERROR creating semaphore semname1");
exit(EXIT_FAILURE);
}
context.empty = sem_open(context.semname2, O_CREAT, 0777, BUFFER_SIZE);
if (context.empty == SEM_FAILED)
{
fprintf(stderr, "%s\n", "ERROR creating semaphore semname2");
exit(EXIT_FAILURE);
}
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
sem_unlink(context.semname1);
sem_unlink(context.semname2);
pthread_exit(NULL);
return 0;
}
Upvotes: 1
Reputation: 182753
When you pass a thread a pointer to a variable, you need to ensure that the lifetime of that variable is at least as long as the thread will attempt to access that variable. You pass the threads pointers to buffer
and context
, which are allocated on the stack inside main
. As soon as main
exits, those variables cease to exist. So you cannot exit from main
until you confirm that those threads no longer need access to those pointers.
95% of the time, the fix for this problem is to follow this simple pattern:
1) Allocate an object to hold the parameters.
2) Fill in the object with the parameters.
3) Pass a pointer to the object to the new thread.
4) Allow the new thread to deallocate the object.
Sadly, this doesn't work well for objects shared by two or more threads. In that case, you can put a use count and a mutex inside the parameter object. Each thread can decrement the use count under protection of the mutex when it's done. The thread that drops the use count to zero frees the object.
You would need to do this for both buffer
and context
. Set the use count to 2
and then pass a pointer to this object to both threads.
Upvotes: 0
Reputation: 279225
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...
Upvotes: 4
Reputation: 753515
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.
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.
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...
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.
pthread_join()
calls' behaviour.Upvotes: 3
Reputation: 84151
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.
Upvotes: 0
Reputation: 881537
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;-).
Upvotes: 0