一、实验题目
生产者—消费者问题
我们提出了一种使用有界缓冲区的信号量解决方案来解决生产者消费者问题。你将使用图 5.9 和 5.10 中所示的生产者和消费者进程来设计有界缓冲区问题的编程解决方案。5.7.1 节中介绍的解决方案使用三个信号量:empty 和 full,它们计算缓冲区中空槽和满槽的数量,以及 mutex,它是一个二进制(或互斥)信号量,用于保护实际插入或删除缓冲区中的项目。你将使用标准计数信号量表示empty和full,并使用互斥锁而不是二进制信号量来表示mutex。生产者和消费者作为单独的线程运行——将产品移入和移出与empty、full和mutex结构同步的缓冲区。可以使用 Pthreads 或 Windows API 实现。
二、相关原理与知识
数信号量: 该计数表示可用的资源的个数。 信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为1就变成互斥锁Mutex,即同时只能有一个任务可以访问信号量保护的共享资源。
三、实验过程
设计算法:
使用互斥锁和信号量来实现生产者-消费者模型的线程同步。生产者和消费者线程共享一个循环队列,生产者线程向队列中插入数据,消费者线程从队列中移除数据,同时需要保证在多线程环境下对共享资源的安全访问。通过互斥锁来保护共享资源的访问,通过信号量来进行生产者和消费者线程之间的同步,保证生产者和消费者线程之间的合作。
代码思路:
在全局定义区定义循环队列和相关的信号量、互斥锁。
编写插入和移除函数,通过互斥锁来对共享资源进行保护,避免多个线程同时访问导致数据竞争。实现生产者和消费者线程的函数,通过信号量来进行线程之间的同步,生产者线程在队列不满的时候进行生产,消费者线程在队列不空的时候进行消费。在main函数中初始化相关的数据结构和线程,创建生产者和消费者线程,并让它们交替执行一段时间。
运行程序并进行验证和调试,确保实现的生产者-消费者模型能够正确地工作。
四、实验结果与分析
将生产者数量设置为5,消费者的数量设置为6,初始数字为3,在缓冲区为空时,消费者不能再进行消费。在缓冲区为满时,生产者不能再进行生产。在一个线程进行生产或消费时,其余线程不能再进行生产或消费等操作,即保持线程间的同步。
观察实验结果,符合预期。
五、问题总结
1.问题:
在创建生产者和消费者线程时,使用了相同的pthread_t变量pid和cid。这可能导致线程id被覆盖,造成意外的行为。
方案:
这为每个线程创建一个新的pthread_t变量来避免这个问题。
2.互斥锁
要先加互斥锁,再循环wait()操作,为了保证线程间的同步,需要先加锁,等事件可使用后才进行线程相应的操作,此时互斥锁的作用是保证共享资源不会被其他线程访问,否则程序会出现崩溃的情况。
六、源代码
#include <unistd.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#define BUFFER_SIZE 5
#define TRUE 1
typedef int buffer_item;
buffer_item START_NUMBER;
buffer_item buffer[BUFFER_SIZE];
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
sem_t full, empty;
int insertPointer = 0, removePointer = 0;
int insert_item(buffer_item item);
int remove_item(buffer_item* item);
void* producer(void* param);
void* consumer(void* param);
int main(int argc, char* argv[]) {
if (argc != 5) {
fprintf(stderr, "Usage: <sleep time> <producer threads> <consumer threads> <start number>\n");
return -1;
}
int sleepTime = atoi(argv[1]);
int producerThreads = atoi(argv[2]);
int consumerThreads = atoi(argv[3]);
START_NUMBER = atoi(argv[4]);
sem_init(&full, 0, 0);
sem_init(&empty, 0, BUFFER_SIZE);
pthread_t producers[producerThreads], consumers[consumerThreads];
for (int i = 0; i < producerThreads; i++) {
pthread_create(&producers[i], NULL, producer, NULL);
}
for (int j = 0; j < consumerThreads; j++) {
pthread_create(&consumers[j], NULL, consumer, NULL);
}
sleep(sleepTime);
for (int i = 0; i < producerThreads; i++) {
pthread_cancel(producers[i]);
}
for (int j = 0; j < consumerThreads; j++) {
pthread_cancel(consumers[j]);
}
sem_destroy(&full);
sem_destroy(&empty);
return 0;
}
int insert_item(buffer_item item) {
buffer[insertPointer] = item;
insertPointer = (insertPointer + 1) % BUFFER_SIZE;
return 0;
}
int remove_item(buffer_item* item) {
*item = buffer[removePointer];
removePointer = (removePointer + 1) % BUFFER_SIZE;
return 0;
}
void* producer(void* param) {
buffer_item item;
while (TRUE) {
sleep(2);
sem_wait(&empty);
pthread_mutex_lock(&mutex);
item = START_NUMBER++;
insert_item(item);
printf("Producer %lu produced %d\n", pthread_self(), item);
pthread_mutex_unlock(&mutex);
sem_post(&full);
}
return NULL;
}
void* consumer(void* param) {
buffer_item item;
while (TRUE) {
sleep(2);
sem_wait(&full);
pthread_mutex_lock(&mutex);
remove_item(&item);
printf("Consumer %lu consumed %d\n", pthread_self(), item);
pthread_mutex_unlock(&mutex);
sem_post(&empty);
}
return NULL;
}