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
我将空 sem 设置为 10,因此将其视为 buf 限制。 但为什么输出会超出限制呢?
一切都按照课本去做。 在 macbook air m1 上,所以 sem_init 不起作用,请使用 sem_open 代替
脚本的主要问题似乎是其信号量操作失败,但程序没有注意识别这一点。
sem_open
、sew_wait
和 sem_post
函数等都通过其返回值传达成功/失败信息,正如许多 C 函数所做的那样。需要监控这些信息并采取适当的行动,以避免程序出现不当行为。
这不是以后要添加的内容。你应该在第一次编写代码时这样做,因为
此时您正在考虑所需的行为,并准备好决定对各种错误的适当处理
此时您的代码最有可能存在错误,正确的错误处理可以帮助您识别和诊断
实际上,您可能不会稍后再回来进行所有适当的错误处理。
在这种情况下,您的
sem_open()
调用缺乏正确的错误处理是一个错误,因为在您的情况下很可能出现此类失败,并且当它们发生时,您的程序无法获得它所需的信号量行为。对 sem_wait()
和 sem_post()
调用缺乏正确的错误处理会导致问题表现为神秘的不当行为,而不是信息丰富的诊断。
话虽如此,一个重要的问题是您的程序不可避免地无法正确管理其信号量。当你使用命名信号量时,你必须
采取有效措施确保它们仅由打算使用它们的进程使用。
使用
O_CREAT
和 O_EXCL
标志打开可以防止两个进程同时使用相同的信号量,这会有所帮助,但它可能会锁定其他进程使用相同名称的信号量。这对于您的特定程序来说是双向的。
适当管理他们的可见性和生命周期。
这很大程度上相当于在最合适的时间
sem_unlink()
关闭它们——即在您确定所有想要打开特定信号量的进程都已这样做之后尽快打开它们。与取消链接常规文件一样,取消链接信号量不会破坏它。这只会将其与名称空间分离,以便它无法被任何更多进程打开。那些已经打开它的人,或者通过 sem_open()
以外的方式获得访问权限的人,可以继续使用它,直到关闭它,无论是明确还是通过终止。当没有进程再打开它们时,系统会自动清理已取消链接的sem。
为大量函数调用编写错误处理代码可能会变得乏味,而更新大量错误处理代码可能会非常痛苦。因此,人们使用宏来帮助生成所有错误处理似乎很常见。这样做的另一个好处是可以防止程序的主要行为被大量样板错误处理所掩盖。有很多方法可以处理细节,这是其中之一:
#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;
}