linux c 生产者与消费者问题 信号量(sem_t)

时间:2022-12-11 15:11:22

参考:“深入理解计算机系统” 第670页


信号量:

信号量提供两种特殊得操作P(s)与V(s)。

P(s):如果s为非零,P将s减一,然后立即返回。如果s为零,那么就挂起线程,直到s变为非零,而V操作会重启这条线程。在重启之后,P操作将s减1,并将控制权返回给调用者。

V(s):V操作将s加1。如果有任何线程P等待s变为非零的话,那么V就会重启其中一条线程,然后将程序的s减一,完成它的P操作。

在 linux semaphore.h 中的P和V操作, P(s)=sem_wait(sem_t*),V(s)=sem_post(sem_t*),下面我们用到的。


生产者与消费者问题:

1。什么是生产者与消费者问题?就是生产者和消费者共用一个缓冲区,生产者生产项目放进缓冲区,而消费者则从缓冲区中消费项目。当缓冲区满了的时候,生产者不能对其生产;而当缓冲区为空的时候,消费者不能作消费。


2。这个问题在现实系统中也运用得非常广泛。例如,在一个多媒体系统中,生产者编码视频帧,而消费者解码并在屏幕上呈现出来。缓冲区得目的是为了减少视频流的抖动,而这种抖动是由各个帧的编码和解码时与数据相关的差异引起得。缓冲区为生产者提供了一个槽位置,而为消费者提供了一个以编码的帧池。


实战代码:

缓冲区结构:

typedef struct __Buf {
int *buf; // 缓冲区
int n; // 最大缓冲区
int head; // 缓冲区头
int tail; // 缓冲区尾
sem_t items; // 锁定对不存在项目的缓冲区进行操作的消费者
sem_t slots; // 锁定对已满缓冲区进行操作的生产者
sem_t mutex; // 确保同时只能有一个消费者或生产者对缓存区进行操作
} Buf;

生产与消费:

// 生产者插入项目
void insert(Buf *b, int item) {
sem_wait(&b->slots);
sem_wait(&b->mutex);
// 插入数据
b->buf[(++b->tail)%b->n] = item;
sem_post(&b->mutex);
// post等价於把信号量加一,令消费者能对缓冲区操作(说明缓冲区状态为非空)
sem_post(&b->items);
}

// 消费者获取项目
int get(Buf *b) {
int item;
sem_wait(&b->items);
sem_wait(&b->mutex);
// 获取数据(意义上是删除了缓冲区中一个项目)
item = b->buf[(++b->head)%b->n];
sem_post(&b->mutex);
// post等价于把信号量加一,令生产者能对缓冲区操作(说明缓冲区状态为未满)
sem_post(&b->slots);
return item;
}

完整代码:

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

typedef struct __Buf {
int *buf; // 缓冲区
int n; // 最大缓冲区
int head; // 缓冲区头
int tail; // 缓冲区尾
sem_t items; // 锁定对不存在项目的缓冲区进行操作的消费者
sem_t slots; // 锁定对已满缓冲区进行操作的生产者
sem_t mutex; // 确保同时只能有一个消费者或生产者对缓存区进行操作
} Buf;

// 声明缓冲区
Buf g_buf;

// 生产者插入项目
void insert(Buf *b, int item) {
sem_wait(&b->slots);
sem_wait(&b->mutex);
// 插入数据
b->buf[(++b->tail)%b->n] = item;
sem_post(&b->mutex);
// post等价於把信号量加一,令消费者能对缓冲区操作(说明缓冲区状态为非空)
sem_post(&b->items);
}

// 生产者线程
void *manufacturer(void *) {
int i=0;
for(i=0; i<100; ++i) {
insert(&g_buf, i);
}
return 0;
}

// 消费者获取项目
int get(Buf *b) {
int item;
sem_wait(&b->items);
sem_wait(&b->mutex);
// 获取数据(意义上是删除了缓冲区中一个项目)
item = b->buf[(++b->head)%b->n];
sem_post(&b->mutex);
// post等价于把信号量加一,令生产者能对缓冲区操作(说明缓冲区状态为未满)
sem_post(&b->slots);
return item;
}

// 消费者线程
void *consumer(void *) {
int i=0, data;
for(i=0; i<100; ++i) {
data = get(&g_buf);
printf("consumer: %d\n", data);
// 消费者睡300ms, 测试一下看看生产者会不会把缓冲区溢出
usleep(1000*300);
}
return 0;
}

int main() {
pthread_t tid_man, tid_con;

// 设置缓冲区
g_buf.n = 5;
g_buf.buf = (int *)malloc(g_buf.n*sizeof(int));
sem_init(&g_buf.mutex, 0, 1);
sem_init(&g_buf.slots, 0, g_buf.n); // g_buf.n也就是允许插入的数量
sem_init(&g_buf.items, 0, 0);
g_buf.head = -1;
g_buf.tail = -1;

// 创建生产者与消费者线程
pthread_create(&tid_man, 0, manufacturer, 0);
pthread_create(&tid_con, 0, consumer, 0);

// 释放已申请的资源
pthread_join(tid_man, 0);
pthread_join(tid_con, 0);
sem_destroy(&g_buf.items);
sem_destroy(&g_buf.slots);
sem_destroy(&g_buf.mutex);

return 0;
}

备注:本人编程新手,如有出错请多多指点,谢谢。