实验目的
在Linux上使用C语言编写一个多线程程序,并通过信号量机制来模拟解决一个有界缓冲区的生产者—消费者问题。其中一个线程代表生产者,另外一个线程代表消费者,使得生产者和消费者线程可以周而复始的工作而不会出现错误。通过这个过程来了解Linux多线程的工作原理。
思路:
注:首先需要在codeblocks的project-build options-linker seetings中添加(add)pthread库。
实验代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h> // 实现多线程的头文件
#include <semaphore.h> // 实现信号量定义的头文件
#define producerNumber 3 // 生产者的数目
#define consumerNumber 5 // 消费者的数目
#define M 5 // 缓冲区数目
int in = 0; // 生产者放置产品的位置
int out = 0; // 消费者取产品的位置
int buff[M] = {0}; // 缓冲初始化为0, 开始时没有产品
//信号量的数据类型为结构sem_t,它本质上是一个长整型的数
sem_t empty_sem; //同步信号量,还有没有空位可以放进去
sem_t full_sem; // 同步信号量,有没有填好的可以消费
pthread_mutex_t mutex; // 互斥信号量
int producer_id = 0; // 生产者id
int consumer_id = 0; // 消费者id
/* 打印缓冲情况 */
void print()
{
for(int i = 0; i < M; i++)
printf("%d ", buff[i]);
printf("\n");
}
/* 生产者方法 */
void *product()
{
int id = ++producer_id;
while(1)
{
// 用sleep的数量可以调节生产和消费的速度,便于观察
sleep(1);
sem_wait(&empty_sem); // 满不放;
//int sem_wait(sem_t *sem), 以原子操作的方式将信号量的值减1
pthread_mutex_lock(&mutex); //互斥,在成功完成返回零,否则错误
in = in % M;
buff[in] = rand()%10;
printf("生产者%d向%d号缓冲区放入了数据%d: \t", id, in, buff[in]);
print();
++in;
pthread_mutex_unlock(&mutex);// 解除锁定 mutex 所指向的互斥锁
sem_post(&full_sem);
// int sem_post(sem_t *sem) 以原子操作方式将信号量的值加1,调用成功时返回0,失败//返回-1
}
}
/* 消费者方法 */
void *prochase()
{
int id = ++consumer_id;
while(1)
{
sleep(1);// 用sleep的数量可以调节生产和消费的速度,便于观察
sem_wait(&full_sem); // 空不取
pthread_mutex_lock(&mutex);
out = out % M;
printf("消费者%d从%d号缓冲区取出了数据%d: \t", id, out, buff[out]);
buff[out] = 0;
print();
++out;
pthread_mutex_unlock(&mutex);
sem_post(&empty_sem);
}
}
int main()
{
pthread_t id1[producerNumber]; // 声明生产者线程的ID数组
pthread_t id2[consumerNumber]; // 声明消费者线程的ID数组
int i;
int ret1[producerNumber];int ret2[consumerNumber];
int ini1 = sem_init(&empty_sem, 0, M); // 初始化同步信号量
// int sem_init (sem_t * sem, int pshared, unsigned int value);
// sem为指向信号量结构的一个指针;pshared==0确保只能为当前的进程的所有线程共享,//否则多个进程共享,value为信号量的初始值. 调用成功时返回0。
int ini2 = sem_init(&full_sem, 0, 0);
if(ini1 && ini2 != 0) //初始化失败
{
printf("sem init failed \n");
exit(1);
}
//初始化互斥信号量的函数pthread_mutex_init();
int ini3 = pthread_mutex_init(&mutex, NULL);
//int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); 以动态方式创建互斥锁
//参数1:为指向互斥信号量结构的一个指针,参数2: 指定了新建互斥锁的属性,NULL表明使用默认的互斥锁属性(快速互斥锁)
if(ini3 != 0)
{
printf("mutex init failed \n");
exit(1);
}
// 创建producerNumber个生产者线程
for(i = 0; i < producerNumber; i++)
{
ret1[i] = pthread_create(&id1[i], NULL, product, NULL);
// 用ret1[]数组记录是否创建线程成功,若返回值为0表示创建成功,否则返回出错编号
//int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);
//参数1为指向线程标识符(ID号)的指针,参数2用来设置线程的属性,参数3为线程运行函数的地址,参数4为运行函数的参数
if(ret1[i] != 0)
{
printf("product%d creation failed \n", i);
exit(1);
}
}
//创建consumerNumber个消费者线程
for(i = 0; i < consumerNumber; i++)
{
ret2[i] = pthread_create(&id2[i], NULL, prochase, NULL);
if(ret2[i] != 0)
{
printf("prochase%d creation failed \n", i);
exit(1);
}
}
//销毁线程
for(i = 0; i < producerNumber; i++)
{
pthread_join(id1[i],NULL);
//pthread_join()函数来使主线程阻塞以等待其他线程(也就是刚才创建的线程)退出
//函数定义int pthread_join(pthread_t thread, void **retval);
//参数thread: 线程标识符,即线程ID,标识唯一线程。retval: 用户定义的指针,用来存储被等待线程的返回值。
}
for(i = 0; i < consumerNumber; i++)
{
pthread_join(id2[i],NULL);
}
exit(0);
}
修改代码,int buff[M] = {1,2,3,4,5}, int ini1 = sem_init(&empty_sem, 0,0); int ini2 = sem_init(&full_sem, 0, M),即缓冲区一开始已经放满,运行结果为:
即生产者不会在缓冲区满时加入数据。