目录
一、实验目的
实现对经典的生产者—消费者问题的模拟,以便更好的理解经典进程同步问题。
二、实验要求
本实验在一个进程中执行两个线程,一个是生产者线程,一个是消费者线程。生产者线程生产物品,然后将物品放置在一个空缓冲区中供消费者线程消费。消费者线程从缓冲区中获得物品,然后释放缓冲区。
本实验需要三个信号量:
1.互斥信号量mutex:实现进程对缓冲池的互斥使用。
2.信号量Empty:表示缓冲池中空缓冲区的数量。
3.信号量Full:表示缓冲池中满缓冲区的数量。
三、实验步骤
在同一个进程地址空间内执行两个线程。生产者线程生产物品,然后将物品放置在一个空缓冲区中供消费者线程消费。消费者线程从缓冲区中获得物品,然后释放缓冲区。当生产者线程生产物品时,如果没有空缓冲区可用,那么生产者线程必须等待消费者线程释放一个空缓冲区。当消费者线程消费物品时,如果没有满的缓冲区,那么消费者线程将被阻挡,直到新的物品被生产出来。
程序流程图:
生产者
消费者
四、核心代码
主要代码及结果分析。运行结果截图。例如:
假设缓冲区大小为10,生产者、消费者线程若干。生产者和消费者相互等效,只要缓冲池未满,生产者便可将消息送入缓冲池;只要缓冲池未空,消费者便可从缓冲池中取走一个消息。
items代表缓冲区已经使用的资源数,spaces代表缓冲区可用资源数
mutex代表互斥锁
buf[10] 代表缓冲区,其内容类型为item
in、out代表第一个资源和最后一个资源
void *producer( void *arg ) {
while( flag ) {
pthread_mutex_lock( &mutex ); // 加锁,保证条件变量不会因为多线程混乱
while( !spaces ) { // 如果缓冲区已满,等待缓冲区不满的条件变量
pthread_cond_wait( ¬full, &mutex );
}
buf[in] = current++; // 将产品放入缓冲区
in = ( in + 1 ) % 10; // 更新输入指针
items++; // 增加产品数量
spaces--; // 减少空闲空间数量
printf( "producer %zu , current = %d\n", pthread_self(), current );
for( int i = 0; i < 10; i++ ) {
printf( "%-4d", buf[i] );
}
printf( "\n\n" );
pthread_cond_signal( ¬empty ); // 通知缓冲区不空的条件变量
pthread_mutex_unlock( &mutex ); // 解锁
}
pthread_exit( NULL );
}
void *consumer( void *arg ) {
while( flag ) {
pthread_mutex_lock( &mutex ); // 加锁,保证条件变量不会因为多线程混乱
while( !items ) { // 如果缓冲区为空,等待缓冲区不空的条件变量
pthread_cond_wait( ¬empty, &mutex );
}
buf[out] = -1; // 从缓冲区取出产品
out = ( out + 1 ) % 10; // 更新输出指针
current--; // 减少当前产品编号
items--; // 减少产品数量
spaces++; // 增加空闲空间数量
printf( "consumer %zu , current = %d\n", pthread_self(), current );
for( int i = 0; i < 10; i++ ) {
printf( "%-4d", buf[i] );
}
printf( "\n\n" );
pthread_cond_signal( ¬full ); // 通知缓冲区不满的条件变量
pthread_mutex_unlock( &mutex ); // 解锁
}
pthread_exit( NULL );
}
int main() {
memset( buf, -1, sizeof(buf) ); // 初始化缓冲区为-1表示空位
flag = true; // 设置标志为true,表示线程可以运行
pthread_t pro[10], con[10]; // 定义生产者和消费者线程ID数组
int i = 0;
for( int i = 0; i < 10; i++ ) {
pthread_create( &pro[i], NULL, producer, NULL ); // 创建生产者线程
pthread_create( &con[i], NULL, consumer, NULL ); // 创建消费者线程
}
sleep(1); // 让线程运行一段时间
flag = false; // 设置标志为false,表示线程需要停止运行
for( int i = 0; i < 10; i++ ) {
pthread_join( pro[i], NULL ); // 等待生产者线程结束
pthread_join( con[i], NULL ); // 等待消费者线程结束
}
return 0;
}
五、记录与处理
运行结果如下,由于运行结果过长,此处截取部分截图。
六、思考
信号量机制相比其他方法实现进程同步的优缺点?
信号量机制是一种实现进程同步的重要方法,与其他方法相比,它有其独特的优缺点。
优点:
1.精确控制:信号量机制通过计数方式实现对共享资源的精确控制。它允许程序员设定一个资源的最大访问数量,并在达到这个数量时阻止其他进程访问,从而避免了资源的过度竞争和冲突。
2.灵活性强:信号量机制支持多种同步模式,如互斥、同步等。通过调整信号量的初始值和操作方式,可以实现不同的同步需求,适用于各种复杂的并发场景。
3.实现简单:信号量机制的实现相对简单,容易理解和使用。它只需要定义一些基本的操作(如P操作和V操作),并在程序中调用这些操作即可实现进程同步。
缺点:
1.需要公共内存:信号量机制必须有公共内存来存储信号量的值。这意味着它不能直接在分布式操作系统中使用,因为分布式系统没有全局共享的内存空间。这是信号量机制的一个主要限制。
2.编程复杂性:虽然信号量机制的实现相对简单,但在使用它来实现进程同步时,程序员需要仔细考虑如何设置信号量的初始值、如何调用P操作和V操作等。这需要一定的编程经验和技能,否则容易出现死锁、饥饿等问题。
3.难以调试和维护:由于信号量机制的实现涉及多个进程之间的交互和协作,因此它的调试和维护相对困难。一旦出现错误或问题,需要仔细分析多个进程的执行轨迹和状态信息,才能找到问题的根源并进行修复。
与其他方法相比,信号量机制在精确控制和灵活性方面具有一定的优势。然而,它也存在一些限制和缺点,如需要公共内存、编程复杂性和难以调试等。因此,在选择使用信号量机制实现进程同步时,需要根据具体的应用场景和需求进行权衡和选择。
七、完整报告和成果文件提取链接
链接:https://pan.baidu.com/s/1UbP6729pCluscVW0_9oI8w?pwd=1xki
提取码:1xki