问题描述

    有读者和写者两个并发进程,共享一个文件,当两个或以上的读进程同时访问共享数据时不会产生副作用,

但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:
1、允许多个读者可以同时对文件进行读操作;
2、只允许一个写者往文件中写信息;
3、任一写者完成之前不允许其他读者或写者工作;
4、写者执行写操作前,应让已有的读者和写者全部退出。

其大概关系如下图所示:

问题分析

  • 关系分析。由问题分析,读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥问题。

  • 整理思路。两个进程,即读者和写者。写者是比较简单的,它和任何进程互斥,用互斥信号量的P、V操作即可解决。读者的问题比较复杂,它必须在实现与写者互斥的同时,实现与其他读者的同步,因此简单的一对P、V操作是无法解决问题的。需要用到一个计时器,用来判断当前是否有读者读文件。当有读者读文件时,写者是无法写文件的,此时读者会一直占用文件,当没有读者时,写者才可以写文件。同时,不同的读者对于计时器的访问也应该是互斥的。

  • 信号量设置。首先设置信号量readerCount为计数器,用于记录当前读者的数量,初值为0;设置mutex为互斥信号量,用于保护更新count变量时的互斥;设置互斥信号量rmutex,wmutex,用于保证读者和写者的互斥访问。

伪代码

  • wait (num),num是目标参数,wait的作用是使其(信息量)减一。如果信息量>=0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。
  • signal (num),num是目标参数,signal的作用是使其(信息量)加一。 如果信息量>0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。

semaphore rmutex = 1,wmutex;//互斥信号量,解决读者和写者之间的互斥信号量wmutex,为readerCount设置信号量rmutex;
int readerCount = 0;//读者数量readerCount全局变量(共享资源)
//编写读者进程的操作

void Reader(){
    do{
        wait(rmutex);//为readerCount设置的信号量
        if(readerCount == 0)//如果现在没有读者在读,就要开启读者-写者互斥锁,防止写者抢占了资源
        wait(wmutex);//解决读者写者互斥问题
        readerCount++;//读者数量加一
        signal(rmutex);//释放读者数量互斥资源
        //...
        //perform read operation;
        //操作一般要长时间,sleep(毫秒数)
        //...
        wait(rmutex);
        readercount--;
        if(readerCount == 0)
        signal(wmutex);//
        signal(rmutex);//如果没有读者在读了,就允许写者写了,这样就可以把互斥锁打开了。
    }while(TRUe);
}
 
//编写写者进程
void Writer(){//写者与任何进程互斥,简单的P、V操作即可实现
    do{
        wait(wmutex);//读者写者互斥冲突信号量
        //...
        //perform write operation
        //操作一般要长时间,sleep(毫秒数)模拟
        //...
        signal(wmutex);
    }while(TRUE);
}
 
void main(){
       cobegin()
        Reader();
        Writer();
       coend
}

伪代码描述

使用线程同步实现上述算法

对于线程,有以下流程:定义--创建初始化-线程运行函数--线程退出

对于信号量,有以下流程:定义--创建初始化--信号量wait和signal--信号量销毁

信号量为什么要销毁:因为要防止资源泄密,信号量是用来解决进程互斥资源共享冲突的,所以信号量的值可以表示当前某些进程之间共享资
源的使用情况,若不销毁,随后无意中改变了信号量的值,可能会导致进程间死锁现象。

对于一个进程,可以在该进程中创建多个线程,多个线程之间共享资源,资源申请就存在互斥关系,需要有锁的概念,信号量可以实现锁。

对于写者线程pthread_t writerTidp;定义了了该线程writerTidppthread_create(&writerTidp,NULL,writerThread,NULL) 创建了该线程,并指明线程函数入口是writerThread,该函数入口声明如下static void *writerThread(void *arg);

相关函数
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr, 
void *(*start_routine) (void *),
void *arg);

pthread_join() 函数声明在<pthread.h>头文件中,语法格式如下:

各个参数的含义是:

  1. pthread_t *thread:传递一个 pthread_t 类型的指针变量,也可以直接传递某个 pthread_t 类型变量的地址。pthread_t 是一种用于表示线程的数据类型,每一个 pthread_t 类型的变量都可以表示一个线程。

  2. const pthread_attr_t *attr:用于手动设置新建线程的属性,例如线程的调用策略、线程所能使用的栈内存的大小等。大部分场景中,我们都不需要手动修改线程的属性,将 attr 参数赋值为 NULL,pthread_create() 函数会采用系统默认的属性值创建线程。
    pthread_attr_t 类型以结构体的形式定义在<pthread.h>头文件中

  3. void *(*start_routine) (void *):以函数指针的方式指明新建线程需要执行的函数,该函数的参数最多有 1 个(可以省略不写),形参和返回值的类型都必须为 void* 类型。void* 类型又称空指针类型,表明指针所指数据的类型是未知的。使用此类型指针时,我们通常需要先对其进行强制类型转换,然后才能正常访问指针指向的数据。

  4. void *arg:指定传递给 start_routine 函数的实参,当不需要传递任何数据时,将 arg 赋值为 NULL 即可。

如果成功创建线程,pthread_create() 函数返回数字 0,反之返回非零值。各个非零值都对应着不同的宏,指明创建失败的原因,常见的宏有以下几种:

  • EAGAIN:系统资源不足,无法提供创建线程所需的资源。
  • EINVAL:传递给 pthread_create() 函数的 attr 参数无效。
  • EPERM:传递给 pthread_create() 函数的 attr 参数中,某些属性的设置为非法操作,程序没有相关的设置权限。

以上这些宏都声明在 <errno.h> 头文件中,如果程序中想使用这些宏,需提前引入此头文件。

int pthread_join(pthread_t thread, void ** retval);

thread 参数用于指定接收哪个线程的返回值;retval 参数表示接收到的返回值,如果 thread 线程没有返回值,又或者我们不需要接收 thread 线程的返回值,可以将 retval 参数置为 NULL。

pthread_join() 函数会一直阻塞调用它的线程,直至目标线程执行结束(接收到目标线程的返回值),阻塞状态才会解除。如果 pthread_join() 函数成功等到了目标线程执行结束(成功获取到目标线程的返回值),返回值为数字 0;反之如果执行失败,函数会根据失败原因返回相应的非零值,每个非零值都对应着不同的宏,例如:

  • EDEADLK:检测到线程发生了死锁。
  • EINVAL:分为两种情况,要么目标线程本身不允许其它线程获取它的返回值,要么事先就已经有线程调用 pthread_join() 函数获取到了目标线程的返回值。
  • ESRCH:找不到指定的 thread 线程。

代码实现

实现多读者(多线程)操作

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
 
sem_t rmutex,wmutex;
static void *readerThread(void *arg);
static void *reader3Thread(void *arg);
static void *reader2Thread(void *arg);
static void *writerThread(void *arg);
int readcount = 0;
int n = 0;
int nowLen = 1;
char contentArticle[10][100];
int main(){
 
	pthread_t readerTidp,writerTidp,reader3Tidp,reader2Tidp;
	void *retval;
	
	if(sem_init(&rmutex,0,1)==-1||sem_init(&wmutex,0,1)==-1){
		printf("sem_init error\n");
		return -1;
	}//init semaphore
 
	if(pthread_create(&readerTidp,NULL,readerThread,NULL) !=0||pthread_create(&writerTidp,NULL,writerThread,NULL) !=0||pthread_create(&reader3Tidp,NULL,reader3Thread,NULL) !=0||pthread_create(&reader2Tidp,NULL,reader2Thread,NULL) !=0){
		printf("pthread_create error\n");
		return -2;
	}//init pthread
 
	pthread_join(readerTidp,&retval);
	pthread_join(reader3Tidp,&retval);
	pthread_join(reader2Tidp,&retval);
	pthread_join(writerTidp,&retval);
 
	sem_destroy(&rmutex);
	sem_destroy(&wmutex);
	return 0;
}
 
static void *readerThread(void *arg){
	for(int i = 0;i < 10;i++)
	{
		sem_wait(&rmutex);
		if(readcount == 0)sem_wait(&wmutex);
		readcount = readcount+1;
		sem_post(&rmutex);
	
		//read operatiom
		printf("\n\nI'm reader first Reader thread :...the global variable  n equals to %d\n",n);
		for(int j = 0;j < nowLen-1;j++)
		{
			for(int k = 0;k < 26;k++)
				printf("%c",contentArticle[j][k]);
			printf("\n");
		}
			printf("now the count 0f reader is %d\n",readcount);
		printf("now the length 0f content is %d\n",nowLen-1);
		sleep(5);
 
		sem_wait(&rmutex);
		readcount = readcount-1;
		if(readcount == 0)sem_post(&wmutex);
		sem_post(&rmutex);
		sleep(1);
	}
}
 
static void *reader3Thread(void *arg){
        for(int i = 0;i < 10;i++)
        {
                sem_wait(&rmutex);
                if(readcount == 0)sem_wait(&wmutex);
                readcount = readcount+1;
                sem_post(&rmutex);
 
                //read operatiom
                printf("\n\nI'm reader third  Reader thread :...the global variable  n equals to %d\n",n);
		for(int j = 0;j < nowLen-1;j++)
                {
                      for(int k = 0;k < 26;k++)
                                printf("%c",contentArticle[j][k]);
                        printf("\n");
                }
                printf("now the count 0f reader is %d\n",readcount);
                printf("now the length 0f content is %d\n",nowLen-1);
 
		sleep(5);
                sem_wait(&rmutex);
                readcount = readcount-1;
                if(readcount == 0)sem_post(&wmutex);
                sem_post(&rmutex);
                sleep(8);
        }
}
 
 
static void *reader2Thread(void *arg){
        for(int i = 0;i < 10;i++)
        {
                sem_wait(&rmutex);
                if(readcount == 0)sem_wait(&wmutex);
                readcount = readcount+1;
                sem_post(&rmutex);
 
                //read operatiom
                printf("\n\nI'm reader second Reader thread :...the global variable  n equals to %d\n",n);
		for(int j = 0;j < nowLen-1;j++)
              {
                        for(int k = 0;k < 26;k++)
                                printf("%c",contentArticle[j][k]);
                        printf("\n");
                }
 
                printf("now the count 0f reader is %d\n",readcount);
                printf("now the length 0f content is %d\n",nowLen-1);
 
 
                sem_wait(&rmutex);
                readcount = readcount-1;
                if(readcount == 0)sem_post(&wmutex);
                sem_post(&rmutex);
                sleep(4);
        }
}
 
 
 
static void *writerThread(void *arg){
        for(int i = 0;i < 10;i++)
        {
                sem_wait(&wmutex);
              
	       	//writer operation 
	       	n = n+1;
		for(int k = 0;k < 26;k++)
		contentArticle[nowLen-1][k] = 'z'-k;
		nowLen++;
		printf("\n\nWriter thread :writing opration the global variable  n equals to  %d \n",n);
                sleep(2);
	       	sem_post(&wmutex);
                sleep(3);
        }
}

运行结果如下: