Ошибка сегментации (дамп ядра) у производителя и потребителя с использованием семафора

Я изучаю системное программирование 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;
}

person Melvin Levett    schedule 25.09.2018    source источник
comment
запустить в gdb может быть, а затем bt   -  person Potato    schedule 25.09.2018
comment
Для таких ошибок используйте средство очистки (в данном случае ThreadSanitizer).   -  person hellow    schedule 25.09.2018
comment
Также обратите внимание, что операторы printf(), такие как printf("------ consumer: %lu, %d\n", pthread_self(), pdel->data);, не гарантируют, что они будут генерироваться атомарно, поэтому вы, вероятно, также получите чередующийся вывод, если ваш код запускает потоки одновременно. Ваш опубликованный код не должен одновременно запускать потоки производителя и потребителя, поэтому это не станет очевидным.   -  person Andrew Henle    schedule 25.09.2018
comment
Это не причина ваших проблем, но вы не должны вызывать printf внутри блокировки семафора, это будет ужасно неэффективно. Вместо этого возьмите семафор, скопируйте соответствующую часть в локальную переменную, освободите семафор, напечатайте локальную переменную.   -  person Lundin    schedule 25.09.2018
comment
@Lundin, ты прав, я позабочусь об этом в следующий раз.   -  person Melvin Levett    schedule 25.09.2018


Ответы (2)


Этот

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

Могут быть и другие ошибки, которые я не идентифицировал.

person Andrew Henle    schedule 25.09.2018
comment
@hellow Если вы собираетесь изменить значение ответа, прокомментируйте, почему. Я недостаточно проанализировал код OP, чтобы заявить, что инициализация семафора со значением 1 вместо 3 даст правильные результаты. - person Andrew Henle; 25.09.2018
comment
... Ты уверен? sem_init(&sem_producer, 0, 3); позволит producer() зациклиться три раза... разве проблема не в том, чтобы защитить один ресурс (head) двумя семафорами? - person Attie; 25.09.2018
comment
@Attie И этот цикл будет происходить одновременно с потоком потребителя, также изменяющим связанный список. Я недостаточно проанализировал код, чтобы определить, что произойдет, когда он попадет в это состояние. - person Andrew Henle; 25.09.2018
comment
@Andrew Henle Состояние гонки, я понял. Итак, как я могу избежать этого, я имею в виду, что я должен сделать, чтобы безопасно максимизировать буфер для производителя, потому что я не хочу, чтобы семафор был только один. - person Melvin Levett; 25.09.2018
comment
Да, но в этот момент это будет список из одного (почему бы просто не иметь указатель). Что предположительно не является тем, что OP хочет от такого доказательства концепции.. (Я полагаю) - person Attie; 25.09.2018
comment
@AndrewHenle Справедливости ради: если вы не уверены в правильности своего ответа, вам следует публиковать не ответ, а краткий комментарий. Однако вполне вероятно, что это правильный ответ. - person Lundin; 25.09.2018
comment
@attie Может быть, а может и нет. Я считаю, что производитель может оставаться на три итерации впереди потребителя, и оба они будут изменять список одновременно, хотя я недостаточно проанализировал код, чтобы быть уверенным. - person Andrew Henle; 25.09.2018
comment
@Lundin Это определенно была ошибка, которая могла привести к наблюдаемому сбою SEGV и, следовательно, к ответу. Я просто не уверен, что это единственный ответ. - person Andrew Henle; 25.09.2018
comment
@AndrewHenle Хороший вопрос, проблем может быть больше. Вы должны отменить редактирование. - person Lundin; 25.09.2018
comment
@Melvin Итак, как мне избежать этого, я имею в виду, что я должен сделать, чтобы безопасно увеличить буфер для производителя Вы можете использовать мьютекс для частей потоков, которые обращаются к связанному списку, исключить производителя семафор и использовать только потребительский семафор, чтобы сообщить потребительскому потоку, что в связанном списке есть по крайней мере один элемент, над которым нужно работать. - person Andrew Henle; 25.09.2018
comment
@AndrewHenle хорошее решение, я просто забыл, что должен использовать блокировку для защиты критической зоны. - person Melvin Levett; 25.09.2018

Я думаю, что основная проблема заключается в следующем:

  • У вас есть один ресурс - head
  • Вы «защищаете» его двумя отдельными семафорами — sem_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().

person Attie    schedule 25.09.2018
comment
Как было опубликовано в настоящее время, если pdel равно NULL, а поток-производитель ожидает на семафоре sem_producer, потоки заблокируются. При наличии мьютекса логически безопасно полностью исключить семафор sem_producer. На самом деле, если поток-производитель зайдет слишком далеко вперед, в игру может вступить максимально возможное значение семафора. - person Andrew Henle; 25.09.2018
comment
@Attie Я думаю, что оператор if после метода pthread_mutex_unlock в потребителе вообще не нужен, потому что у меня есть семафор sem_consumer, чтобы гарантировать, что у меня определенно есть элемент, когда sem_wait не заблокирован. И вы пропустили освобождение pdel в потребителе. - person Melvin Levett; 25.09.2018
comment
Я думаю, что оператор if после метода pthread_mutex_unlock в потребителе вообще не нужен - возможно, но если вы принимаете сценарий что, если, то наличие теста не позволит вашему приложению сбой... sem_wait() может вернуться по причинам, не связанным с у вас есть щелчок. - person Attie; 25.09.2018
comment
вы пропустили освобождение pdel в потребителе - я удалил управление памятью из своего примера кода... должно быть много больше проверок возвращаемого значения!... опущено для краткости :-) - person Attie; 25.09.2018