pthreads в C - pthread_exit - PullRequest
       7

pthreads в C - pthread_exit

14 голосов
/ 25 июля 2010

Почему-то я думал, что вызов 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);
}

Ответы [ 9 ]

18 голосов
/ 25 июля 2010

pthread_exit() - это функция, вызываемая потоком для прекращения собственного выполнения.Для ситуации, которую вы задали, ее нельзя вызывать из основного потока программы.

Как вы выяснили, pthread_join() является правильным средством ожидания завершения присоединяемого потока из * 1005.**pthread_t переменная для всех потоков, которые вы создаете, если вы собираетесь использовать pthread_join().

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

4 голосов
/ 25 июля 2010

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

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

А также:

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

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

3 голосов
/ 25 июля 2010

Мини-сага

Вы не упоминаете среду, в которой вы запускаете оригинальный код.Я изменил ваш код для использования 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'); 'vis' взято из 'Среды программирования UNIX' Kernighan & Pike; '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 'Bad file descriptor');Сначала я добавил туда чеки.Затем я проверил инициализацию ...

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

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

Морали

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

Нет необходимости вообще вызывать pthread_join(reader,NULL);, если Context и buffer объявлены со статической продолжительностью хранения (как уже указали Стив Джессоп, caf и David Schwartz).

Объявление Context и buffer static также приводит к необходимости изменить Context *context на Context *contextr или Context *contextw соответственно.

Кроме того, следующая перезапись под названием pthread_exit.c заменяет sem_init() на sem_open() и использует nanosleep() (как предложил Джонатан Леффлер).

pthread_exit был протестирован на Mac OS X 10.6.8 и не выводил никаких символов ASCII NUL.

/*

cat pthread_exit.c  (sample code to test pthread_exit() in main())

source: 
"pthreads in C - pthread_exit",
/2826034/pthreads-v-c-pthreadexit

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;
}
1 голос
/ 25 июля 2010

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

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

0 голосов
/ 29 августа 2011

Когда вы передаете потоку указатель на переменную, вы должны убедиться, что время жизни этой переменной не меньше, чем поток будет пытаться получить доступ к этой переменной.Вы передаете указатели потоков на buffer и context, которые расположены в стеке внутри main.Как только main выходит, эти переменные перестают существовать.Таким образом, вы не можете выйти из main, пока не подтвердите, что этим потокам больше не нужен доступ к указателям.

95% времени, решение этой проблемы состоит в следующем:

1) Выделите объект для хранения параметров.

2) Заполните объект параметрами.

3) Передайте указатель на объект в новый поток.

4) Разрешить новому потоку освободить объект.

К сожалению, это плохо работает для объектов, совместно используемых двумя или более потоками.В этом случае вы можете поместить счетчик использования и мьютекс в объект параметра.Каждый поток может уменьшить счетчик использования под защитой мьютекса, когда это будет сделано.Поток, который сбрасывает счетчик использования до нуля, освобождает объект.

Это необходимо сделать как для buffer, так и для context.Установите для счетчика использования значение 2, а затем передайте указатель на этот объект в оба потока.

0 голосов
/ 25 июля 2010

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

0 голосов
/ 25 июля 2010

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

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

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

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

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

0 голосов
/ 25 июля 2010

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.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...