Я изучаю системное программирование Linux, поэтому я просто попытался написать очень распространенную программу, которая является производителем и потребителем. Я использовал механизм семафоров для реализации своего кода. К сожалению, у меня есть ошибка сегментации, который я действительно не мог найти, где я ошибался. Надеюсь, кто-то может мне помочь, спасибо.
Код, как показано ниже:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem_consumer;
sem_t sem_producer;
typedef struct node
{
int data;
struct node *next;
} Node;
Node * head = NULL;
void * producer(void *arg)
{
while (1)
{
sem_wait(&sem_producer);
Node *ptr = (Node *)malloc(sizeof(Node));
ptr->data = rand() % 1000;
printf("++++++ producer: %lu, %d\n", pthread_self(), ptr->data);
ptr->next = head;
head = ptr;
sem_post(&sem_consumer);
}
return NULL;
}
void * consumer(void * arg)
{
while (1)
{
sem_wait(&sem_consumer);
Node *pdel = head;
head = head -> next;
printf("------ consumer: %lu, %d\n", pthread_self(), pdel->data);
free(pdel);
sem_post(&sem_producer);
}
return NULL;
}
int main(int argc, char *argv[])
{
sem_init(&sem_consumer, 0, 0);
sem_init(&sem_producer, 0, 3);
pthread_t pthid[2];
pthread_create(&pthid[0], NULL, producer, NULL);
pthread_create(&pthid[1], NULL, consumer, NULL);
for (int i = 0; i < 2; i++)
{
pthread_join(pthid[i], NULL);
}
sem_destroy(&sem_consumer);
sem_destroy(&sem_producer);
return 0;
}
Для этого типа ошибок используйте sanitzier (в данном случае ThreadSanitizer)
Также обратите внимание, что такие операторы printf(), как printf("------ consumer: %lu, %d\n", pthread_self(), pdel->data);, не гарантируются атомарно, поэтому вы, вероятно, также получите чередующийся вывод, если ваш код запускает потоки одновременно. Ваш опубликованный код не должен запускать потоки производителя и потребителя одновременно, поэтому это не станет очевидным.
Это не причина ваших проблем, но вы не должны вызывать printf внутри семафорной блокировки, это будет ужасно неэффективно. Вместо этого возьмите семафор, скопируйте соответствующую часть в локальную переменную, отпустите семафор, распечатайте локальную переменную.
@Lundin, ты прав, я позабочусь об этом в следующий раз.





Этот
sem_init(&sem_producer, 0, 3);
инициализирует семафор sem_producer до 3, позволяя потокам производителя и потребителя одновременный доступ к коду, например
ptr->next = head;
head = ptr;
а также
Node *pdel = head;
head = head -> next;
printf("------ consumer: %lu, %d\n", pthread_self(), pdel->data);
free(pdel);
Ваш связанный список не защищен семафором должным образом. Это состояние гонки.
Инициализация sem_producer на 1 исправит состояние гонки:
sem_init(&sem_producer, 0, 1);
Могут быть и другие ошибки, которые я не обнаружил.
@hellow Если вы собираетесь изменить смысл ответа, прокомментируйте, почему. Я недостаточно проанализировал код OP, чтобы заявить, что инициализация семафора значением 1 вместо 3 даст правильные результаты.
... вы уверены? sem_init(&sem_producer, 0, 3); позволит producer() выполнить три цикла ... не проблема ли больше в защите одного ресурса (head) с помощью двух семафоров?
@Attie И этот цикл будет происходить одновременно с тем, что поток потребителя также изменит связанный список. Я недостаточно проанализировал код, чтобы определить, что происходит, когда он переходит в это состояние.
@ Эндрю Хенле Состояние гонки, я понял. Итак, как я могу этого избежать? Я имею в виду, что я должен сделать, чтобы максимально безопасно увеличить размер буфера для производителя, потому что я не хочу, чтобы семафор имел только один.
Да, но тогда это будет список из одного (почему бы просто не иметь указатель). Какой предположительно не то, что OP хочет от такого доказательства концепции .. (я полагаю)
@AndrewHenle Честно говоря: если вы не уверены, что ваш ответ правильный, вы не должны публиковать ответ, а вместо этого краткий комментарий. Однако вполне вероятно, что это правильный ответ.
@attie Может быть, а может и нет. Я считаю, что производитель может оставаться на три итерации раньше потребителя, и оба будут изменять список одновременно, хотя я на самом деле недостаточно проанализировал код, чтобы быть уверенным.
@Lundin Это определенно была ошибка, которая могла привести к наблюдаемому отказу SEGV и, следовательно, к ответу ан. Я просто не уверен, что это ответ Только.
@AndrewHenle Хорошее замечание, проблем может быть больше. Вы должны откатить правку.
@Melvin Итак, как я могу этого избежать, я имею в виду, что я должен сделать, чтобы максимально безопасно увеличить буфер для производителя Вы можете использовать мьютекс для частей потоков, которые обращаются к связанному списку, исключить семафор производителя и использовать только семафор потребителя, чтобы сообщить потоку потребителя, что в связанном списке есть хотя бы один элемент для работы.
@AndrewHenle хорошее решение, я просто забыл, что я должен использовать замок для защиты критической зоны.
Думаю, главная проблема:
headsem_producer и sem_consumerСемафоры используются для сигнализации «ты можешь взять что-нибудь» или «ты можешь положить что-нибудь», контролируя использование ресурсов - например, если вы хотите гарантировать, что список никогда не будет глубже трех объектов. Это особенно важно, когда потоки производителя / потребителя имеют разное время выполнения. Если вы не слишком озабочены производством множества товаров, за которыми потребитель, возможно, никогда не успеет, то вы можете полностью удалить sem_producer.
Мьютексы («взаимное исключение») используются для того, чтобы два потока не обрабатывали один объект одновременно. Я бы посоветовал вам использовать мьютекс вокруг манипуляций с указателем рядом с семафорами.
pthread_mutex_t mux;
void *producer(void *arg) {
while (1) {
sem_wait(&sem_producer);
/* gather data */
pthread_mutex_lock(&mux);
p_new->next = head;
head = p_new;
pthread_mutex_unlock(&mux);
sem_post(&sem_consumer);
}
return NULL;
}
void *consumer(void *arg) {
while (1) {
sem_wait(&sem_consumer);
pthread_mutex_lock(&mux);
p_next = head;
if (p_next != NULL) {
head = p_next->next;
}
pthread_mutex_unlock(&mux);
/* skip if there isn't actually a new item */
if (p_next != NULL) {
/* do processing and discard */
}
sem_post(&sem_producer);
}
return NULL;
}
Не забудьте вызвать в pthread_mutex_init() и pthread_mutex_destroy().
Как сообщается в настоящее время, если pdel - это NULL, а поток производителя ожидает семафор sem_producer, потоки зайдут в тупик. При наличии мьютекса логически безопасно полностью исключить семафор sem_producer. В действительности, если поток-производитель заходит слишком далеко вперед, в игру может вступить максимально возможное значение семафора.
@Attie Я думаю, что оператор if после метода pthread_mutex_unlock в потребителе вообще не нужен, потому что у меня есть семафор sem_consumer, чтобы гарантировать, что у меня есть элемент определенно, когда sem_wait не заблокирован. И вы пропустили освобождение pdel в потребителе.
«Я думаю, что оператор if после метода pthread_mutex_unlock у потребителя вообще не нужен» - возможно, но если вы принимаете сценарий «что, если», то после тестирования он предотвратит сбой вашего приложения ... sem_wait() может вернуться по причинам, помимо «ты попал».
"вы пропустили освобождение pdel в потребителе" - я удалил управление памятью из моего примера кода ... должно быть намного больше проверок возвращаемого значения! ... опущено для краткости :-)
запустить в gdb может быть, а затем bt