По некоторым причинам я думал тот вызов 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);
}
pthread_exit ()
- это функция, вызываемая потоком для завершения своего выполнения. В данной ситуации он не должен вызываться из основного потока программы.
Как вы уже выяснили, pthread_join ()
является правильным средством ожидания завершения присоединяемого потока из main ()
.
Также, как вы уже поняли, вам необходимо сохранить значение, возвращаемое из pthread_create ()
, для передачи в pthread_join ()
.
Это означает, что вы не можете использовать одну и ту же переменную pthread_t
для всех потоков, которые вы создаете, если вы собираетесь использовать pthread_join ()
.
Вместо этого создайте массив pthread_t
, чтобы у вас была копия идентификатора каждого потока.
Вы не упоминаете среду, в которой вы запускаете исходный код. Я изменил ваш код, чтобы использовать 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 ()
завершился успешно, что выглядит неплохо (names "/ full.sem"
и "/ empty.sem"
, флаги O_CREAT, значения режима 0444, 0600, 0700 в разное время и начальные значения 0 и BUFFER_SIZE, как в случае sem_init ()
).К сожалению, первая операция sem_wait ()
или sem_post ()
снова завершается ошибкой с errno = 9 (EBADF 'Плохой дескриптор файла').
pthread_join ()
». pthread_join ()
- стандартный способ ожидания завершения другого потока, я бы придерживался этого.
В качестве альтернативы, вы можете создать счетчик потоков и сделать так, чтобы все дочерние потоки увеличивали его на 1 в начале, затем уменьшали его на 1, когда они заканчивают (конечно, с надлежащей блокировкой), а затем использовать main ()
подождите, пока этот счетчик достигнет нуля (я бы выбрал pthread_cond_wait ()
).
В соответствии с нормальной семантикой pthread
, как показано, например, здесь , ваша первоначальная идея, похоже, подтверждается:
Если main () завершается раньше, чем потоки он создал, и выходит с pthread_exit (), другие потоки будут продолжать выполнять. В противном случае они будет автоматически прекращено, когда main () завершается.
Однако я не уверен, является ли это частью стандарта потоков POSIX или просто обычным, но не универсальным лакомым кусочком "приятно иметь" (я знаю, что некоторые реализации не соблюдают это ограничение - я просто не знаю, должны ли эти реализации все же считаться совместимыми со стандартами! -). Так что мне придется присоединиться к благоразумному хору, рекомендующему присоединение каждого потока, который необходимо завершить, на всякий случай - или, как выразился Джон Постел в контексте TCP / IP. реализации:
Be conservative in what you send; be liberal in what you accept.
«принцип устойчивости», который следует использовать более широко, чем только в TCP / IP ;-).
pthread_exit (3)
завершает поток, который его вызывает (но не весь процесс, если другие потоки все еще работают). В вашем примере другие потоки используют переменные в стеке main
, таким образом, когда поток main
завершается и его стек уничтожается, они обращаются к неотображенной памяти, следовательно, к segfault.
Используйте правильную технику pthread_join (3)
, как предлагают другие, или переместите общие переменные в статическое хранилище.
pthread_join
делает следующее :
Функция
pthread_join()
приостанавливает выполнение вызывающего потока до завершения целевого потока, если целевой поток уже не завершился. По возвращении из успешного вызоваpthread_join()
с аргументом не-NULLvalue_ptr
значение, переданноеpthread_exit()
завершающимся потоком, становится доступным в месте, на которое ссылаетсяvalue_ptr
. Когдаpthread_join()
возвращается успешно, целевой поток был завершен. Результаты нескольких одновременных вызововpthread_join()
, указывающих один и тот же целевой поток, не определены. Если поток, вызывающийpthread_join()
, будет отменен, то целевой поток не будет отсоединен.
Однако вы можете добиться того же, используя легкий цикл, который предотвратит выход exe
. В Glib это достигается созданием GMainLoop, в Gtk+ можно использовать gtk_main.
После завершения потоков вы должны выйти из главного цикла или вызвать gtk_exit
.
В качестве альтернативы вы можете создать собственную функциональность ожидания, используя комбинацию сокетов, труб и системного вызова select, но это не обязательно и может рассматриваться как упражнение для практики.
Помимо того, должна ли программа завершаться, когда основной поток вызывает pthread_exit
, pthread_exit
говорит
Функция pthread_exit () завершается вызывающий поток
А также:
После завершения потока результат доступа к локальному (авто) переменные потока не определены.
Поскольку контекст - это автоматическая переменная main ()
, ваш код может потерпеть неудачу еще до того, как дойдет до стадии тестирования того, что вы хотите проверить ...