实验五线程间的互斥与同步
实验学时:2学时
实验类型:验证、设计型
一、实验目的
理解POSIX线程(Pthread)互斥锁和POSIX信号量机制,学习它们的使用方法;编写程序,实现多个POSIX线程的同步控制。
二,实验内容
创建4个POSIX线程。
其中2个线程(A和B)分别从2个数据文件(data1.txt和data2.txt)读取10个整数. 线程A和B把从文件中读取的逐一整数放入一个缓冲池. 缓冲池由n个缓冲区构成(n=5,并可以方便地调整为其他值),每个缓冲区可以存放一个整数。
另外2个线程,C和D,各从缓冲池读取10数据。
线程C、D每读出2个数据,分别求出它们的和或乘积,并打印输出。
提示:在创建4个线程当中,A和B是生产者,负责从文件读取数据到公共的缓冲区,C和D是消费者,从缓冲区读取数据然后作不同的计算(加和乘运算)。
使用互斥锁和信号量控制这些线程的同步。
不限制线程C和D从缓冲区得到的数据来自哪个文件。
在开始设计和实现之前,务必认真阅读课本6.8.4节和第6章后面的编程项目——生产者-消费者问题。
三,实验要求
按照要求编写程序,放在相应的目录中,编译成功后执行,并按照要求分析执行结果,并写出实验报告。
四,实验设计
1,功能设计
根据实验要求,主程序需要创建四个线程,两个线程负责从文件读取数据到缓冲区,两个线程负责将缓冲区的数据做数学运算。
由于同一个进程中的各个线程共享资源,可以用一个二维数组的全局变量作为公共缓冲区,同时还需要一个整形全局变量size用来做数组的索引。
读线程的运行函数打开不同的文件并从中读取数据到二维数组中,每次写入数组后size加一。
运算线程从二维数组中读数并做运算,每次读数之前size减一。
本题的关键在于如何使用信号量保证进程的同步与互斥。
在运算线程从缓冲区读取之前缓冲区里必须有数,即任意时刻运算操作的执行次数必须小于等于读取操作的执行次数。
同时应该保证两个读线程和两个运算线程两两互斥。
由于以上分析,使用了四个信号量sem1,sem2,sem3和sem4。
sem1保证线程1和线程2互斥,sem2保证线程3和线程4互斥,sem3保证线程3和线程4互斥,sem4保证线程4和线程1互斥。
即这四个信号量使四个线程循环进行,从而保证了运行结果的正确性。
源代码及注释:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define NUM 200
int stack[NUM][2]; //公共缓冲区
int size=0; //初始化数组索引
sem_t sem1,sem2,sem3,sem4; //定义四个信号量
void read1(void){ //线程运行函数,负责从文件读取数据FILE *fp=fopen("data0.txt","r"); //以只读方式打开文件data1
while(!feof(fp)){
sem_wait(&sem1); //减少信号量sem1
if(!fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]))return;
size++; //读两个数到公共缓冲区
sem_post(&sem2); //增加信号量sem2
}
fclose(fp); //关闭文件
}
void read2(void){ //线程运行函数,负责从文件读取数据FILE *fp=fopen("data1.txt","r"); //以只读方式打开文件data2
while(!feof(fp)){
sem_wait(&sem2); //减少信号量sem2
if(!fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]))return;
size++; //读两个数到公共缓冲区
sem_post(&sem3); //增加信号量sem3
}
fclose(fp);
}
void plus1(void){ //线程运行函数,负责加运算
while(1){
sem_wait(&sem3); //减少信号量sem3
if(size==0){return;}
size--;
printf("Plus:%d+%d=%d\n",stack[size][0],stack[size][1],stack[size][0]+stack[size][1] ); //从公共缓冲区取数并进行加运算
sem_post(&sem4); //增加信号量sem3
}
}
void multi2(void){ //线程运行函数,负责乘运算
while(1){
sem_wait(&sem4); //减少信号量sem4
if(size==0){return;}
size--;
printf("Multiply:%d*%d=%d\n",stack[size][0],stack[size][1],stack[size][0]*stack[size ][1]); //从公共缓冲区取数并进行乘运算
sem_post(&sem1); //增加信号量sem3
}
}
int main(void){
pthread_t t1,t2,t3,t4; //定义线程标识符
sem_init(&sem1,0,1); //初始化信号量
sem_init(&sem2,0,0);
sem_init(&sem3,0,0);
sem_init(&sem4,0,0);
pthread_create(&t1,NULL,(void *)read1,NULL); //创建线程,绑定线程运行函数pthread_create(&t2,NULL,(void *)read2,NULL);
pthread_create(&t3,NULL,(void *)plus1,NULL);
pthread_create(&t4,NULL,(void *)multi2,NULL);
pthread_join(t1,NULL); //等待线程运行结束
}
2,数据结构
信号量(semaphore):数据类型为结构 sem_t,本质上是一个长整型的数。
公共缓冲区(stack):采用2维数组的方式实现(stack[NUM][2])。
数组中的两列分别存储两个文件中的数据。
该2维数组还有一个索引:size,指向2维数组的顶部。
五,实验测试结果及分析
实验截图:
如图所示,加线程和乘线程从缓冲区读取数据,进行运算,并将运算结果输出。
六,收获及体会
在本次实验中,我学习了UNIX 类操作系统信号量机制,掌握了编写Linux 环境下利用信号量实现进程控制的方法及相关系统调用的使用方法。
在编译的时候也遇到了一些问题,在读取时data0和data1时出现过读取的错误,在使用信号量对线程控制时出现了一些错误,运算总是出现顺序问题。
通过测试与调试,我找出了错误原因,并修改了程序中的信号量设置方式,严格控制各个线程的执行顺序,终于解决了问题。
在这个过程中我加深了对于信号量操作的理解与认识。
七,参考资料《2011秋季实验指导》。