pthreads in C - pthread_exit

2020-05-26 07:44发布

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);
}

标签: c pthreads
9条回答
Explosion°爆炸
2楼-- · 2020-05-26 07:58

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...

查看更多
唯我独甜
3楼-- · 2020-05-26 08:10

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;
}
查看更多
ゆ 、 Hurt°
4楼-- · 2020-05-26 08:10

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.

查看更多
兄弟一词,经得起流年.
5楼-- · 2020-05-26 08:14

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.

查看更多
孤傲高冷的网名
6楼-- · 2020-05-26 08:14

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).

查看更多
Animai°情兽
7楼-- · 2020-05-26 08:14

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.

查看更多
登录 后发表回答