pthreads в C - pthread_exit

По некоторым причинам я думал тот вызов pthread_exit(NULL) в конце основной функции гарантировал бы, что все рабочие потоки (по крайней мере, созданный в основной функции) закончат работать прежде main мог выйти. Однако, когда я выполняю этот код ниже, не называя два pthread_join функции (в конце main) явно я получаю отказ сегментации, который, кажется, происходит потому что main из функции вышли, прежде чем два потока заканчивают свое задание, и поэтому символьный буфер больше не доступен. Однако, когда я включаю эти два pthread_join вызовы функции в конце main это работает, как это должно. Гарантировать это main не выйдет, прежде чем все рабочие потоки закончились, это необходимый для вызова pthread_join явно для всех потоков, инициализированных непосредственно в 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;
}

Если это так, как я мог обработать случай, где много идентичных потоков (как в коде ниже) было бы создано с помощью того же идентификатора потока? В этом случае, как я могу удостовериться, что все потоки закончатся прежде main выходы? Сделайте я действительно должен сохранить массив NUM_STUDENTS pthread_t идентификаторы, чтобы смочь сделать это? Я предполагаю, что мог сделать это, позволив Студенческим потокам сигнализировать о семафоре и затем позволить main функциональное ожидание на том семафоре, но не там действительно никакой более легкий способ сделать это?

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);
}
14
задан Mooncrater 24 August 2017 в 07:58
поделиться

7 ответов

pthread_exit () - это функция, вызываемая потоком для завершения своего выполнения. В данной ситуации он не должен вызываться из основного потока программы.

Как вы уже выяснили, pthread_join () является правильным средством ожидания завершения присоединяемого потока из main () .

Также, как вы уже поняли, вам необходимо сохранить значение, возвращаемое из pthread_create () , для передачи в pthread_join () .

Это означает, что вы не можете использовать одну и ту же переменную pthread_t для всех потоков, которые вы создаете, если вы собираетесь использовать pthread_join () .

Вместо этого создайте массив pthread_t , чтобы у вас была копия идентификатора каждого потока.

18
ответ дан 1 December 2019 в 09:59
поделиться

Мини-сага

Вы не упоминаете среду, в которой вы запускаете исходный код. Я изменил ваш код, чтобы использовать nanosleep () (поскольку, как я уже упоминал в комментарии к вопросу, sleep () принимает целое число и, следовательно, sleep (0.2) эквивалентен sleep (0) ) и скомпилировал программу на MacOS X 10.6.4.

Без проверки ошибок

Работает нормально; потребовалось около 100 секунд для запуска с коэффициентом вероятности 0,5 (как и следовало ожидать; я изменил его на 0,05, чтобы сократить время выполнения примерно до 10 секунд), и иногда генерировал случайную строку.

Иногда у меня ничего нет, иногда больше, а иногда меньше. Но я не видел дампа ядра (даже с 'ulimit -c unlimited', чтобы разрешить произвольно большие дампы ядра).

В конце концов, я применил некоторые инструменты и увидел, что всегда получаю 1025 символов (1024 сгенерированных плюс новая строка), но довольно часто я получал 1024 символа ASCII NUL.Иногда они появлялись посередине, иногда в начале и т. Д .:

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

(Программа 'tpipe' похожа на 'tee', но записывает в каналы вместо файлов (и в стандартный вывод, если вы не укажете '-s 'option);' vis 'взято из' Среда программирования UNIX 'Кернигана и Пайка;' ww '- это' оболочка слов ', но здесь нет никаких слов, поэтому он перебирает ширину 64.)

Поведение, которое я наблюдал, было весьма неопределенным - я получал разные результаты при каждом запуске. Я даже заменил случайные символы алфавитом по порядку ('a' + i% 26), и все равно получалось странное поведение.

Я добавил отладочный код печати (и счетчик для контекста), и было ясно, что семафор context-> full не работал должным образом для читателя - ему было разрешено войти в взаимное исключение до того, как писатель что-либо написал.

С проверкой ошибок

Когда я добавил проверку ошибок к мьютексным и семафорным операциям, я обнаружил, что:

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

Итак, странные результаты вызваны тем, что MacOS X не реализует sem_init () . Это странно; функция sem_wait () завершилась ошибкой с errno = 9 (EBADF 'Плохой дескриптор файла'); Я сначала добавил туда чеки. Затем я проверил инициализацию ...

Использование sem_open () вместо sem_init ()

Вызов sem_open () завершился успешно, что выглядит неплохо (names "/ full.sem" и "/ empty.sem" , флаги O_CREAT, значения режима 0444, 0600, 0700 в разное время и начальные значения 0 и BUFFER_SIZE, как в случае sem_init () ).К сожалению, первая операция sem_wait () или sem_post () снова завершается ошибкой с errno = 9 (EBADF 'Плохой дескриптор файла').

Мораль

  • Важно проверять состояния ошибок из системных вызовов.
  • Вывод, который я вижу, недетерминирован, потому что семафоры не работают.
  • Это не меняет поведения «не произойдет сбой без вызова pthread_join () ».
  • MacOS X не имеет работающей реализации семафора POSIX.
3
ответ дан 1 December 2019 в 09:59
поделиться

pthread_join () - стандартный способ ожидания завершения другого потока, я бы придерживался этого.

В качестве альтернативы, вы можете создать счетчик потоков и сделать так, чтобы все дочерние потоки увеличивали его на 1 в начале, затем уменьшали его на 1, когда они заканчивают (конечно, с надлежащей блокировкой), а затем использовать main () подождите, пока этот счетчик достигнет нуля (я бы выбрал pthread_cond_wait () ).

1
ответ дан 1 December 2019 в 09:59
поделиться

В соответствии с нормальной семантикой pthread , как показано, например, здесь , ваша первоначальная идея, похоже, подтверждается:

Если main () завершается раньше, чем потоки он создал, и выходит с pthread_exit (), другие потоки будут продолжать выполнять. В противном случае они будет автоматически прекращено, когда main () завершается.

Однако я не уверен, является ли это частью стандарта потоков POSIX или просто обычным, но не универсальным лакомым кусочком "приятно иметь" (я знаю, что некоторые реализации не соблюдают это ограничение - я просто не знаю, должны ли эти реализации все же считаться совместимыми со стандартами! -). Так что мне придется присоединиться к благоразумному хору, рекомендующему присоединение каждого потока, который необходимо завершить, на всякий случай - или, как выразился Джон Постел в контексте TCP / IP. реализации:

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

«принцип устойчивости», который следует использовать более широко, чем только в TCP / IP ;-).

0
ответ дан 1 December 2019 в 09:59
поделиться

pthread_exit (3) завершает поток, который его вызывает (но не весь процесс, если другие потоки все еще работают). В вашем примере другие потоки используют переменные в стеке main , таким образом, когда поток main завершается и его стек уничтожается, они обращаются к неотображенной памяти, следовательно, к segfault.

Используйте правильную технику pthread_join (3) , как предлагают другие, или переместите общие переменные в статическое хранилище.

0
ответ дан 1 December 2019 в 09:59
поделиться

pthread_join делает следующее :

Функция pthread_join() приостанавливает выполнение вызывающего потока до завершения целевого потока, если целевой поток уже не завершился. По возвращении из успешного вызова pthread_join() с аргументом не-NULL value_ptr значение, переданное pthread_exit() завершающимся потоком, становится доступным в месте, на которое ссылается value_ptr. Когда pthread_join() возвращается успешно, целевой поток был завершен. Результаты нескольких одновременных вызовов pthread_join(), указывающих один и тот же целевой поток, не определены. Если поток, вызывающий pthread_join(), будет отменен, то целевой поток не будет отсоединен.

Однако вы можете добиться того же, используя легкий цикл, который предотвратит выход exe. В Glib это достигается созданием GMainLoop, в Gtk+ можно использовать gtk_main. После завершения потоков вы должны выйти из главного цикла или вызвать gtk_exit.

В качестве альтернативы вы можете создать собственную функциональность ожидания, используя комбинацию сокетов, труб и системного вызова select, но это не обязательно и может рассматриваться как упражнение для практики.

0
ответ дан 1 December 2019 в 09:59
поделиться

Помимо того, должна ли программа завершаться, когда основной поток вызывает pthread_exit , pthread_exit говорит

Функция pthread_exit () завершается вызывающий поток

А также:

После завершения потока результат доступа к локальному (авто) переменные потока не определены.

Поскольку контекст - это автоматическая переменная main () , ваш код может потерпеть неудачу еще до того, как дойдет до стадии тестирования того, что вы хотите проверить ...

4
ответ дан 1 December 2019 в 09:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: