Почему семафор пуст == 0, но не блокируется?

классическая проблема производителя-потребителя в C.

#include <semaphore.h>
#include <stdio.h>
#include <pthread.h>

int buf = 0;
sem_t *mutex, *full, *empty;

void *producer(void *arg) {
    while (1) {
        sem_wait(empty); // Wait for an empty slot
        sem_wait(mutex); // Acquire mutex for critical section
        buf++;
        printf("Item produced. Count: %d\n", buf);
        sem_post(mutex); // Release mutex
        sem_post(full); // Signal that an item is available
    }
    return NULL;
}

void *consumer(void *arg) {
    while (1) {
        sem_wait(full); // Wait for an item to be available
        sem_wait(mutex); // Acquire mutex for critical section
        buf--;
        printf("Item consumed. Count: %d\n", buf);
        sem_post(mutex); // Release mutex
        sem_post(empty); // Signal that an empty slot is available
    }
    return NULL;
}

int main() {
    pthread_t producer_thread, consumer_thread;

    // Initialize semaphores
    mutex = sem_open("/mutex", O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, 1);
    full = sem_open("/full", O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, 0);
    empty = sem_open("/empty", O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, 10);

    // Create producer and consumer threads
    pthread_create(&producer_thread, NULL, producer, NULL);
    pthread_create(&consumer_thread, NULL, consumer, NULL);

    // Join threads (never reached in this example due to infinite loop in threads)
    pthread_join(producer_thread, NULL);
    pthread_join(consumer_thread, NULL);

    // Close and unlink semaphores (optional, but good practice)
    sem_close(mutex);
    sem_close(full);
    sem_close(empty);
    sem_unlink("/mutex");
    sem_unlink("/full");
    sem_unlink("/empty");

    return 0;
}

и вот некоторые результаты:

Item consumed. Count: 1926
Item consumed. Count: 1925
Item consumed. Count: 1924
Item produced. Count: 1933
Item produced. Count: 1924
Item consumed. Count: 1923
Item consumed. Count: 1924
Item consumed. Count: 1923
Item consumed. Count: 1922
Item consumed. Count: 1921
Item consumed. Count: 1920

я сделал пустое значение равным 10, поэтому ожидал, что это будет предел буфера. Но почему выход превышает лимит?

делал все по учебнику. на macbook air m1, поэтому sem_init не работает, вместо этого используйте sem_open

Вы не тестируете возвращаемые значения ни одной из ваших операций с семафором. Если бы какой-либо из них потерпел неудачу, вы бы это обычно определили именно так.

John Bollinger 09.04.2024 17:24

1. Не могу повторить. 2. Вы пришли к нам с просьбой исправить ошибки; почему ты не сделал то же самое с ОС? Проверяйте свои звонки на наличие ошибок! 3. Почему вы подаете в суд на sem_open, а не на sem_init? В результате последующие запуски вашей программы завершаются сбоем (из-за сбоя sem_open, поскольку семафор уже открыт, и вы не проверяете наличие ошибок).

ikegami 09.04.2024 17:25

Примечание: вашей программе должна быть предоставлена ​​возможность корректно завершить работу. Если вам придется убить его, чтобы остановить, то он оставит семафоры позади и не сможет открыть их (из-за O_CREAT | O_EXCL) в следующий раз, если вы не очистите их отдельно.

John Bollinger 09.04.2024 17:48

Или, как минимум, unlink каждый семафор, как только вы его открываете, поскольку вам не нужно (или не хочется!) разделять их между процессами. Это не помешает программе использовать их, но позволит избежать их оставления, если только программа не будет завершена между ее открытием и отключением.

John Bollinger 09.04.2024 17:51

спасибо, братан, проблема была в отключении связи. Первая попытка моего кода не закрыла и не отключила семафоры, поэтому они остались в моей ОС. и все последующие попытки продолжают использовать существующие sems. вот почему buff вне диапазона. Решил проблему, запустив специальную программу, которая отсоединяла эти сэмы.

Marshall Hank 10.04.2024 04:48

Я рад, что помог. Но отмечу, что, как я уже заметил, открытие семафоров с помощью O_CREAT | O_EXCL означает, что, если они уже существуют, их открытие не удастся - что ваша программа не пытается обнаружить. Таким образом, вы не используете существующие семафоры. Если семафоры с заданными именами уже существуют, ваша программа вообще не использует семафоры. Все эти операции с семафором терпят неудачу, и программа не обращает на это внимания.

John Bollinger 10.04.2024 20:52
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Основная проблема сценария заключается в том, что его операции с семафорами не работают, но программа не обращает на это внимания. Функции sem_open, sew_wait и sem_post, среди прочих, передают информацию об успехе/неуспехе через свои возвращаемые значения, как и многие другие функции C. Эту информацию необходимо отслеживать и принимать соответствующие меры, чтобы избежать неправильного поведения программы.

И это не то, что можно добавить позже. Вам следует сделать это при первом написании кода, потому что

  • именно тогда вы думаете о требуемом поведении и лучше всего готовы принять решение о соответствующей обработке различных ошибок.

  • именно тогда ваш код, скорее всего, будет содержать ошибки, правильная обработка которых поможет вам распознать и диагностировать их.

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

В этом случае отсутствие правильной обработки ошибок для ваших вызовов sem_open() является ошибкой, поскольку такие сбои в вашем случае весьма вероятны, и когда они происходят, ваша программа не получает требуемого поведения семафора. Отсутствие правильной обработки ошибок для вызовов sem_wait() и sem_post() приводит к тому, что проблема проявляется в виде загадочного неправильного поведения, а не в виде информативной диагностики.

С учетом вышесказанного, одной из проблем является то, что ваша программа неизбежно не сможет должным образом управлять своими семафорами. Когда вы используете именованные семафоры, вы должны

  • принять эффективные меры для обеспечения того, чтобы они использовались только теми процессами, которые предназначены для их использования.

    Открытие с помощью флагов O_CREAT и O_EXCL не позволяет двум процессам использовать один и тот же семафор, что помогает, но создает возможность блокировки других процессов от использования семафоров с одинаковыми именами. Это обоюдоострый подход для вашей конкретной программы.

  • соответствующим образом управлять их видимостью и сроком службы.

    В основном это означает sem_unlink()открыть их в наиболее подходящее время, то есть как можно скорее после того, как вы будете уверены, что все процессы, которые захотят открыть конкретный семафор, сделали это. Как и удаление связи с обычным файлом, удаление семафора не уничтожает его. Это только отделяет его от пространства имен, так что он не может быть открыт другими процессами. Те, у кого он уже открыт или получил доступ с помощью других средств, кроме sem_open(), могут продолжать использовать его до тех пор, пока не закроют его явно или путем прекращения. Система автоматически очистит отсоединенные файлы SE, если они больше не открываются ни одним процессом.

Написание кода обработки ошибок для большого количества вызовов функций может оказаться утомительным, а обновление большого количества кода обработки ошибок может быть совершенно болезненным. В результате люди довольно часто используют макросы для обработки всех этих ошибок. Это дает дополнительное преимущество, заключающееся в том, что основное поведение программы не скрывается за большим количеством шаблонной обработки ошибок. Есть много способов проработать детали, один из них:

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <errno.h>

int buf = 0;
sem_t *mutex, *full, *empty;

#define warn_if (val, cond, context) do { \
    if ((val) cond) { \
        perror("warning: " context); \
    } \
} while (0)

#define exit_if (val, cond, context) do { \
    if ((val) cond) { \
        perror(context); \
        exit(1); \
    } \
} while (0)

#define do_or_die(func, paren_args) { \
    exit_if ((func) paren_args, == -1, #func); \
}

#define pt_do_or_die(func, paren_args) { \
    exit_if (errno = (func) paren_args, != 0, #func); \
}

sem_t *create_semaphore(const char *name, unsigned int initial_val) {
    sem_t *sem = sem_open(name, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, initial_val);
    
    exit_if (sem, == SEM_FAILED, "sem_open");
    warn_if (sem_unlink(name), == -1, "sem_unlink");

    return sem;
}

void *producer(void *arg) {
    while (1) {
        do_or_die(sem_wait, (empty)); // Wait for an empty slot
        do_or_die(sem_wait, (mutex)); // Acquire mutex for critical section
        buf++;
        printf("Item produced. Count: %d\n", buf);
        do_or_die(sem_post, (mutex)); // Release mutex
        do_or_die(sem_post, (full)); // Signal that an item is available
    }
    return NULL;
}

void *consumer(void *arg) {
    while (1) {
        do_or_die(sem_wait, (full)); // Wait for an item to be available
        do_or_die(sem_wait, (mutex)); // Acquire mutex for critical section
        buf--;
        printf("Item consumed. Count: %d\n", buf);
        do_or_die(sem_post, (mutex)); // Release mutex
        do_or_die(sem_post, (empty)); // Signal that an empty slot is available
    }
    return NULL;
}

int main() {
    pthread_t producer_thread;
    pthread_t consumer_thread;

    // Initialize semaphores
    mutex = create_semaphore("/mutex", 1);
    full = create_semaphore("/full", 0);
    empty = create_semaphore("/empty", 10);

    // Create producer and consumer threads
    pt_do_or_die(pthread_create, (&producer_thread, NULL, producer, NULL));
    pt_do_or_die(pthread_create, (&consumer_thread, NULL, consumer, NULL));

    // Join threads (never returns in this example due to infinite loop in threads)
    pt_do_or_die(pthread_join, (producer_thread, NULL));
    pt_do_or_die(pthread_join, (consumer_thread, NULL));

    // Close semaphores (optional, but good practice)
    warn_if (sem_close(mutex), == -1, "sem_close");
    warn_if (sem_close(full), == -1, "sem_close");
    warn_if (sem_close(empty), == -1, "sem_close");

    return 0;
}

Другие вопросы по теме