赵颢凯 柴玉梅
摘要:在计算机操作系统的学习过程中,生产者—消费者问题向来是難点。结合Linux系统提供的互斥锁机制,编写C语言程序实现生产者—消费者问题,并对运行结果进行了详细分析,旨在帮助学习者更好地理解该问题,为掌握其他进程同步与互斥问题奠定基础。
关键词:生产者—消费者问题;进程同步;Linux;互斥锁
中图分类号:TP316 文献标识码:A
文章编号:1009-3044(2021)36-0132-03
开放科学(资源服务)标识码(OSID):
Using Linux Mutex Mechanism to Solve Producer-consumer Problem
ZHANG Hao-kai, CHAI Yu-mei
(School of Computer and Software Engineering,University of Science and Technology Liaoning, Anshan 114051, China)
Abstract: In the process of learning computer operating system, the producer-consumer problem has always been difficult . Based on the mutex mechanism provided by Linux, a C program is written to solve the problem.And the results are analyzed in detail to help learners better understand the problem and lay foundation for mastering other process synchronization and mutex problems.
Key words:producer-consumer problem; process synchronization; Linux; mutex
1 引言
生产者—消费者问题是操作系统中一个经典的进程同步问题。该问题是指有若干个生产者和消费者线程,连接在可数个单位缓冲区的有界环状缓冲上,故又称有界缓冲问题。在缓冲区内生产者线程所产生的产品不断地被投入,只要缓冲区未空,消费者线程就会不断地从缓冲区中取走或消费产品[1]。
在学习的过程中,笔者发现自己及周围的很多同学对此都不甚理解。因此想借助Linux系统提供的互斥锁机制,设计一个C语言的程序来更好地理解该问题。
2 生产者—消费者问题描述
2.1 二者的关系图
生产者线程与消费者线程关系如图1所示。
2.2 问题分析
生产者线程和消费者线程对缓冲区进行操作时,如果未加以限制,就会造成缓冲区结果不唯一。并且两者的交替的执行会导致线程之间永远的等待,造成系统出现死锁的状态。原因是两者之间访问缓冲区的速度不匹配,需要调整并发的线程的执行速度,这种关系也被叫作线程同步。
3 Linux互斥锁解决生产者—消费者问题
3.1 涉及的函数
表1列出了解决该问题所需的Linux API[2]。
3.2 代码实现
3.2.1 设计思路
变量P_MEMBER,C_MEMBER分别控制生产者、消费者数量,NUMBER表示缓冲区的大小,循环控制两者对缓冲区buff的操作次数也就是局部变量j。线程对缓冲区buff[NUMBER]中的数据进行+1、-1操作。全局变量in、out则控制二者在缓冲区的位置。当生产者进行操作时,空则buff[in]+1,满则释放互斥锁;消费者进行操作时,满则buff[in]-1,空则释放互斥锁。
3.2.2 程序清单
#include "stdio.h"
#include "pthread.h"
pthread_cond_t g_empty = PTHREAD_COND_INITIALIZER; //条件变量初始化
pthread_cond_t g_full = PTHREAD_COND_INITIALIZER; //条件变量初始化
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; //互斥锁初始化
#define P_MEMBER 3
#define C_MEMBER 1
#define NUMBER 6
int buff[NUMBER] = { 0 }; //缓冲区大小
int producer_id = 0; //生产者线程ID
int customer_id = 0; //消费者线程ID
int in = 0;
int out = 0;
//生产者方法
void* producer()
{
int id = ++producer_id; //分配生产者ID
int j = 0; //限制生产者操作次数
while (j < 4)
{
sleep(1); //调节生产者消费者速度便于观察
pthread_mutex_lock(&g_mutex); //上锁
in = in % NUMBER;
while (buff[in] == 1) //缓冲区满,释放互斥锁,消费者线程操作
{
printf("buff[%d] is full,producer %d is waiting for customer.\n", in, id);
pthread_cond_wait(&g_full, &g_mutex);
}
printf("producer %d put into buff[%d]. buff[%d]+1 \t\n", id, in,in);
buff[in] += 1;
in += 1;
pthread_cond_signal(&g_empty); //生產出资源,唤醒条件变量
pthread_mutex_unlock(&g_mutex); //解锁
j++;
}
}
//消费者方法
void* customer()
{
int id = ++customer_id; //分配生产者ID
int j = 0; //限制消费者操作次数
while (j < 12)
{
sleep(1);
pthread_mutex_lock(&g_mutex);
out = out % NUMBER;
while (buff[out] == 0) //缓冲区空,释放互斥锁,生产者线程操作
{
printf("buff[%d] is empty,customer %d is waiting for producer\n", out, id);
pthread_cond_wait(&g_empty, &g_mutex);
}
printf("customer %id take out buff[%d]. buff[%d]-1 \t\n", id, out.out);
buff[out] -= 1;
out += 1;
pthread_cond_signal(&g_full); //消费了资源,唤醒条件变量
pthread_mutex_unlock(&g_mutex);
j++;
}
}
int main(void)
{
int i, p_ret[P_MEMBER], c_ret[C_MEMBER];
pthread_attr_t p_attr[P_MEMBER], c_attr[C_MEMBER]; //定义生产者消费者线程
pthread_t p_tid[P_MEMBER], c_tid[C_MEMBER]; //初始化生产者消费者线程ID
for (i = 0;i < P_MEMBER;++i)
{
pthread_attr_init(&p_attr[i]); //初始化生产者线程
pthread_attr_setdetachstate(&p_attr[i], PTHREAD_CREATE_DETACHED);
}
for (i = 0;i < C_MEMBER;++i)
{
pthread_attr_init(&c_attr[i]); //初始化消费者线程
pthread_attr_setdetachstate(&c_attr[i], PTHREAD_CREATE_DETACHED);
}
//创建MEMBER个生产者线程
for (i = 0;i < P_MEMBER;++i)
{
p_ret[i] = pthread_create(&p_tid[i], &p_attr[i], producer, (void*)(&i));
if (p_ret[i] != 0)
{
printf("producer error code:%d\n", i);
}
}
//创建MEMBER个消费者线程
for (i = 0;i < C_MEMBER;++i)
{
c_ret[i] = pthread_create(&c_tid[i], &c_attr[i], customer, NULL);
if (c_ret[i] != 0)
{
printf("customer error code:%d\n", i);
}
}
pthread_exit(NULL);
}
3.3 结果分析
在VMware Workstation虚拟机中装载的CentOS-7-64中编译、运行该程序,某次运行的部分结果如图2所示。
图2中的(1)表明第一个到达的是消费者,初始时缓冲区是空的,所以customer 1要等待。随后,陆续到达一批生产者放产品入缓冲区。当buff[0]中有产品时,会唤醒customer 1,如图2中的(2)所示。图2中的(3)表明当某个缓冲区位置满时,生产者要等待,另外,还實现了多个生产者对同一个缓冲区位置的互斥访问。图2中的(4)和(5)则表示当消费者取走产品后,唤醒等待的生产者。
3.4 深入理解
改变生产者、消费者的数量及缓冲区的大小可以对生产者—消费者问题进行更深入的理解。
3.4.1供求基本平衡的情况
修改生产者、消费者数量为2,改变缓冲区大小为buff[4],修改每个生产者、消费者执行次数为2。某次输出结果如图3所示。
多次运行程序,都会得到类似的结果,因此可初步断定供求基本平衡时,可能不会出现等待状态。
3.4.2供大于求的情况
修改生产者数量为3,消费者数量为1,改变缓冲区大小为buff[6],修改每个生产者执行次数4、消费者执行次数为12。某次部分输出结果如图4所示。
多次运行程序,大都会有生产者处于等待的状态。
3.4.3供不应求的情况
修改生产者数量为1,消费者数量为3,改变缓冲区大小为buff[3],修改每个生产者执行次数为3、消费者执行次数为1。某次部分输出结果如图5所示。
多次运行程序,大都会有消费者处于等待的状态。
4 结语
本文使用Linux提供的互斥锁机制,设计、编写程序解决生产者—消费者问题。详细分析了供求基本平衡、供大于求及供不应求时,生产者与消费者如何竞争、抢占和等待资源。笔者及同学们通过此程序对这个经典的进程同步问题有了更直观 的理解。但此程序未能实现封装,操作不便,不利于多次使用。这也是笔者下一步要解决的问题。
参考文献:
[1] 费翔林,骆斌.操作系统教程[M].5版.北京:高等教育出版社,2014.
[2] 文全刚.嵌入式 Linux 操作系统原理与应用[M].北京:北京航天航空大学出版社,2011.
[3] Andrew S Tanenbaum.Modern Operating Systems[M]. Englewood,Pearson,2007.
[4] Randal E Bryant/David O`Hallaron.深入理解计算机[M].3版.北京:机械工业出版社,2016.
[5] 李梅.生产者-消费者的Linux多线程实现[J].价值工程,2012,31(30):221-222.
【通联编辑:王力】