📖 操作系统实验—— 典型同步问题模拟处理编程设计与实现

Lab6: 典型同步问题模拟处理编程设计与实现

1. 实验目的

探索、理解并掌握操作系统同步机制的应用编程方法,针对典型的同步问题,构建基于Windows(或 Linux)操作系统同步机制的解决方案。

2. 实验要求

了解、熟悉和运用 Windows(或 Linux)操作系统同步机制及编程方法,针对典型的同步问题,譬如生产者-消费者问题、读者优先的读者-写者问题、写者优先的读者-写者问题、读者数限定的读者-写者问题、哲学家就餐问题等(任选四个即可),编程模拟实现相应问题的解决方案。

3. 实验内容

3.1 生产者-消费者问题

3.1.1 问题描述

系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品(数据)并使用。

  • 生产者、消费者共享一个初始为空、大小为n的缓冲区。
  • 只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。
  • 有缓冲区不空时,消费者才能从中取出产品,否则必须等待。
  • 缓冲区是临界资源,各进程必须互斥地访问。

在这里插入图片描述

伪代码描述:

semaphore mutex = 1; // 互斥信号量,实现对缓冲区的互斥访问
semaphore empty = n; // 同步信号量,表示空闲缓冲区的数量
semaphore full = 0;	// 同步信号量,表示非空缓冲区的数量
// 生产者
producer(){
    while(1){
        ProduceProduct();//生产一个产品
     	P(empty); // 消耗一个空闲缓冲区
        P(mutex); // 缓冲区互斥访问
        PutProduct();//把产品放入缓冲区
        V(mutex); // 缓冲区互斥访问
        V(full); // 增加一个产品
    }
}
// 消费者
consumer(){
    while(1){
        P(full); //消耗一个产品
        P(mutex); // 缓冲区互斥访问
        GetProduct(); // 从缓冲区中取出一个产品
        V(mutex); // 缓冲区互斥访问
        UseProduct(); // 使用产品
    }
}
3.1.2 编程实现

首先设置信号量用于进程的同步和互斥,具体实现如下:

int n = 10;	// 单个缓存区大小
int in[3] = { 0,0,0 }, out[3] = { 0,0,0 }; // 生产指针,消费指针
int flag[3] = { 0,0,0 }; // 分别对应三个缓冲区所存放的数据个数,初始时都为0
int buffer[3][10]; // 3个缓存区(分别是1,2,3)每个缓冲区可以存放10个数据
mutex mtx[3]; // 缓冲区互斥量
mutex data_mtx;	// 输出数据锁

然后实现生产者函数,代码和注释如下:

void producer(int id)
{
    while(1){
        for (int i = 0; i < 3; i++) // 依次遍历三个缓冲区,看哪一个缓冲区满足写条件
            {
            if (mtx[i].try_lock()) // 如果可以对第i个缓冲区上锁
                {
                if (count[i]!=n) // 且第i个缓冲区不满
                    {
                    buffer[i][in[i]] = 1; // 第i个缓冲区写指针对应的位置写为1
                    display_mtx.lock(); // 输出前上锁
                    cout << "【Producer" << id << "】: is producing 【product" << in[i] <<"】at 【Buffer"<<i+1 <<"】"<< endl;
                    cout << "【Buffer"<<i+1 << "】"<<":";
                    for (int j = 0; j < n; j++)
                    {
                        cout << buffer[i][j] << " "; // 遍历输出第i个缓冲区的值
                    }
                    cout<<endl<<"---------------------------------------------------------------------"<<endl;
                    display_mtx.unlock();// 输出后解锁

                    in[i] = (in[i] + 1) % n; // 第i个缓冲区的写指针循环遍历
                    count[i]++; // 第i 个缓冲区的个数加1
                    mtx[i].unlock(); // 对第i个缓冲区解锁

                    this_thread::sleep_for(chrono::seconds(1)); // 延迟1s
                    break;
                    }
                else // 第i个缓冲区已满
                {
                    mtx[i].unlock(); // 对第i个缓冲区解锁
                }
                }
            }
    }
}

然后实现生产者函数,代码和注释如下:

void consumer(int id)
{
    while(1){
        for (int i = 0; i < 3; i++) // 依次遍历三个缓冲区,看哪一个缓冲区满足读条件
            {
            if (mtx[i].try_lock()) // 试探第i个缓冲区,并上锁
                {
                if (count[i] != 0) // 如果第i个缓冲区不为空
                    {
                    buffer[i][out[i]] = 0; // 将都指针指向的位置写入0,即消费
                    display_mtx.lock(); // 写数据上锁
                    cout << "【Consumer" << id << "】: is consuming 【product" << out[i] <<"】from 【Buffer"<<i+1 <<"】"<< endl;
                    cout << "【Buffer"<<i+1 << "】" << ":";
                    for (int j = 0; j < n; j++)
                    {
                        cout << buffer[i][j] << " "; // 输出第i个缓冲区的值
                    }
                    cout<<endl<<"---------------------------------------------------------------------"<<endl;
                    display_mtx.unlock(); // 写数据解锁

                    out[i] = (out[i] + 1) % n; // 读指针后移
                    count[i]--;// 第i个缓冲区的个数减1
                    mtx[i].unlock(); // 缓冲区解锁

                    this_thread::sleep_for(chrono::seconds(5)); // 等待5s
                    break;
                    }
                else // 第i个缓冲区为空,则解锁
                {
                    mtx[i].unlock();
                }
                }
            }
    } ;
}
3.1.3 结果分析

运行环境:本地Windows电脑上的CLion编译器

运行结果如下图所示,生产者和消费者之间互斥的进行临界资源的访问,且当缓冲区满时,则无法继续生产;当缓冲区为空时,则无法继续消费。

在这里插入图片描述

3.2 读者写者问题(读者优先、写者优先)

3.2.1 问题描述

有读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。读者写者问题基本要求如下:

  • 允许多个读者可以同时对文件执行读操作(读读允许)

  • 只允许一个写者往文件中写信息(写写互斥)

  • 任一写者在完成写操作之前不允许其他读者或写者工作(读写互斥)

  • 读者优先

    附加条件:如果一个读者申请进行读操作时已有另一读者正在进行读操作,则该读者可直接开始读操作。

    伪代码描述如下:

/* 读者优先 */
//定义信号量
semaphore mutex =1; // 对R_count的互斥操作
semaphore RP_Write=1; // 保证读者优先,读写互斥
int R_Count=0; // 当前写者的数量

// 读者优先——读者
Reader(){
    while(1){
        wait(mutex); // 上锁,防止多个进程同时操作R_count
        R_Count++;// 读者数量加1
        if(R_Count==1) // 如果当前只有一个读者进程
            wait(RP_Write); // 则该进程负责上锁,不允许写进程执行
        signal(mutex); // 释放锁,允许其他进程操作R_count

        readFile();//读临界区

        wait(mutex);// 上锁,防止多个进程同时操作R_count
        R_Count--;// 读者数量减1
        if(R_count==0) // 如果当前读者进程数为0
            signal(RP_Write); // 则最后一个离开的进程负责解锁,此时允许写进程执行
        signal(mutex);  // 释放锁,允许其他进程操作R_count        
    }
}

//写者优先——写者
Writer(){
    while(1){
        wait(RP_Write); // 上锁,此时不允许其他的读写操作
        WriteFile(); // 写临界区  
        signal(RP_Write); // 解锁,开放其他的读写操作        
    }
}
  • 写者优先

    附加条件:如果一个读者申请进行读操作时已有另一写者在等待访问共享资源,则该读者必须等到没有写者处于等待状态后才能开始读操作。

    伪代码如下:

/* 写者优先 */
//定义信号量
semaphore mutex1 = 1; // 保证每个读者按顺序进入临界区
semaphore mutex2 = 1; // 对R_Count的互斥操作
semaphore mutex3 = 1; // 对W_Count的互斥操作
semaphore WP_Read = 1; // 写者优先,读者和写者互斥
semaphore WP_Write=1; // 如果有读者正在读,写者等待当前读者读完后再写
int R_count=0; // 读者进程数
int W_count=0; // 写者进程数
//写者优先——读者
Reader(){
	while(1){
        wait(mutex1);//上锁,写者之间互斥
        wait(WP_Read);// 上锁,读者写者互斥
        wait(mutex2); // 上锁,不允许其他进程操作R_count
        R_count++; // 读者数量加1
        if(R_count==1) // 如果当前只有一个读者
            wait(WP_Write); // 则第一个读者负责上锁,不允许写操作
        signal(mutex2); // 解锁,允许其他进程操作R_count
        signal(WP_Read); 
        signal(mutex1);
        
        ReadFile();// 读临界区
        
        wait(mutex2); //上锁,不允许其他进程操作R_count
        R_Count--; // 读者数量减1
        if(R_Count==0)// 如果当前没有读者
            signal(WP_Write); //则可以开始写操作
        signal(mutex2); // 解锁,允许其他进程操作R_count
    }
}

//写者优先——写者
Writer(){
	while(1){
       wait(mutex3); // 上锁,不允许其他进程操作R_count
       W_count++; //写者数量加1
       if(W_count==1){ // 如果当前只有一个写者
           wait(WP_Read);// 则第一个读者负责上锁,不允许读操作
       }
       signal(mutex3);// 解锁,允许其他进程操作R_count
        
       wait(WP_Write);// 上锁,不允许其他的进程写
       WriteFile();// 写操作
       signal(WP_Write);// 解锁,允许其他的进程写
       
       wait(mutex3); // 上锁,不允许其他进程操作R_count
       W_Count--; //写者数量减1
       if(W_count==0)  // 如果当前没有写进程
           signal(WP_Read); // 则可以进行读操作
       signal(mutex3); // 解锁,允许其他进程操作R_count
    }
}
3.2.2 编程实现
  • 定义相关的数据结构和全局数据:

    int  R_Count = 0;//读者数目
    int  W_Count = 0;//写者数目
    CRITICAL_SECTION  RP_Write;//读者优先——写者互斥
    CRITICAL_SECTION  WP_Write;// 写者优先——写者互斥
    CRITICAL_SECTION  WP_Read;//写者优先——读者互斥
    
    struct  ThreadInfo//线程结构
    {
    	int  id;//线程序号
    	char  type;//线程类别(判断是读者线程还是写者线程)
    	double  delay;//线程延迟
    	double  persist;//线程读写操作持续时间
    };
    
3.2.2.1 读者优先
  • 读者优先代码

    • 读者线程

      读者线程的具体代码实现和注释如下:

      void RP_ReaderThread(void* p)
      {
      	//h_Mutex 保证对R_Count的操作互斥
      	HANDLE h_Mutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "mutex_for_R_Count");
      
      	DWORD wait_for_mutex;//等待互斥变量所有权
      	DWORD m_delay;//延迟时间
      	DWORD m_persist;//读文件持续时间
      	int m_id;//线程序号
      
      	//从参数中获得读者线程的信息
      	m_id = ((ThreadInfo*)(p))->id;
      	m_delay = (DWORD)(((ThreadInfo*)(p))->delay * INTE_PER_SEC);
      	m_persist = (DWORD)(((ThreadInfo*)(p))->persist * INTE_PER_SEC);
      	Sleep(m_delay);//延迟等待
      	printf("【Reader】 thread 【%d】 is 【requesting】...\n", m_id);
      
      	wait_for_mutex = WaitForSingleObject(h_Mutex, -1);//wait操作,保证对R_Count的操作互斥
      	R_Count++;//读者数目增加1
      	if (R_Count == 1) // 如果当前只有一个读者进程
      	{
      		EnterCriticalSection(&RP_Write); // 则该进程负责上锁,不允许写进程执行
      	}
      	ReleaseMutex(h_Mutex);// signal操作,释放锁,允许其他进程操作R_count
      	printf("【Reader】 thread 【%d】 begins 【reading】...\n", m_id);
      	
      	Sleep(m_persist); // 模拟读文件延时
      	printf("【Reader】 thread 【%d】has 【finished】\n", m_id);// 读取完成,线程退出
      
      	wait_for_mutex = WaitForSingleObject(h_Mutex, -1);// 上锁,防止多个进程同时操作R_count
      	R_Count--;// 读者数量减1
      	if (R_Count == 0) // 如果当前读者进程数为0
      	{
      		LeaveCriticalSection(&RP_Write); // 则最后一个离开的进程负责解锁,此时允许写进程执行
      	}
      	ReleaseMutex(h_Mutex);// 释放锁,允许其他进程操作R_count
      }
      
    • 写者线程

      写者线程的具体代码实现和注释如下:

      /*
      读者优先 - 写者线程
      p: 写者线程信息
      */
      //
      
      void RP_WriterThread(void* p)
      {
      	DWORD m_delay;//延迟时间
      	DWORD m_persist;//写文件持续时间
      	int m_id;//线程序号
      	//从参数中获得信息
      	m_id = ((ThreadInfo*)(p))->id;
      	m_delay = (DWORD)(((ThreadInfo*)(p))->delay * INTE_PER_SEC);
      	m_persist = (DWORD)(((ThreadInfo*)(p))->persist * INTE_PER_SEC);
      	Sleep(m_delay);//延迟等待
      	printf("【Write】 Thread 【%d】 is 【requesting】....\n", m_id);
      	EnterCriticalSection(&RP_Write);// 上锁,此时不允许其他的读写操作
      	printf("【Write】 Thread 【%d】begins 【writing】\n", m_id);//写文件(临界区)
      	Sleep(m_persist); //模拟写文件延时
      	printf("【Write】 Thread 【%d】 has 【finished】\n", m_id);//写操作结束,退出线程
      	LeaveCriticalSection(&RP_Write);// 解锁,开放其他的读写操作 
      }
      
    • 读者优先处理函数

      void ReaderPriority(char* file)
      {
      	DWORD n_thread = 0;//线程数目
      	DWORD thread_ID;//线程ID
      	DWORD wait_for_all;//等待所有线程结束
      
      	HANDLE h_Mutex=CreateMutex(NULL, FALSE, "mutex_for_R_Count");
      
      	//线程对象的数组
      	HANDLE h_Thread[MAX_THREAD_NUM];
      	ThreadInfo thread_info[MAX_THREAD_NUM];
      
      	R_Count = 0;//初始化R_Count
      	InitializeCriticalSection(&RP_Write);//初始化临界区
      	ifstream inFile;
      	inFile.open(file);//打开文件
      	printf("Reader Priority:\n\n");
      	while (inFile)
      	{
      		//读入每一个读者、写者的信息
      		inFile >> thread_info[n_thread].id;
      		inFile >> thread_info[n_thread].type;
      		inFile >> thread_info[n_thread].delay;
      		inFile >> thread_info[n_thread++].persist;
      		inFile.get();
      	}
      	for (int i = 0; i < (int)(n_thread); i++)
      	{
      		if (thread_info[i].type == READER || thread_info[i].type == 'r')
      		{
      			//创建读者进程
      			h_Thread[i] = CreateThread(NULL, 0,
      				(LPTHREAD_START_ROUTINE)(RP_ReaderThread),
      				&thread_info[i],
      				0, &thread_ID);
      		}
      		else 
      		{
      			//创建写者进程
      			h_Thread[i] = CreateThread(NULL, 0,
      				(LPTHREAD_START_ROUTINE)(RP_WriterThread),
      				&thread_info[i],
      				0, &thread_ID);
      		}
      	}
      	//等待所有线程结束
      	wait_for_all = WaitForMultipleObjects(n_thread, h_Thread, TRUE, -1);
      	printf("All done!\n");
      }
      
3.2.2.2 写者优先
  • 写者优先代码

    • 读者线程

      void WP_ReaderThread(void* p)
      {
      	//互斥变量
      	HANDLE h_mutex1=OpenMutex(MUTEX_ALL_ACCESS, FALSE, "mutex1");
      	HANDLE h_mutex2= OpenMutex(MUTEX_ALL_ACCESS, FALSE, "mutex2");
      
      	DWORD wait_for_mutex1;//等待互斥变量所有权
      	DWORD wait_for_mutex2;
      	DWORD m_delay;//延迟时间
      	DWORD m_persist;//读文件持续时间
      	int m_id;//线程序号
      	//从参数中获得信息
      	m_id = ((ThreadInfo*)(p))->id;
      	m_delay = (DWORD)(((ThreadInfo*)(p))->delay * INTE_PER_SEC);
      	m_persist = (DWORD)(((ThreadInfo*)(p))->persist * INTE_PER_SEC);
      	Sleep(m_delay);//延迟等待
      
      	printf("【Reader】 Thread 【%d】 is 【requesting】....\n", m_id);
      	wait_for_mutex1 = WaitForSingleObject(h_mutex1, -1);//wait操作
      	EnterCriticalSection(&WP_Read);//wait操作,进入读者临界区
      	wait_for_mutex2 = WaitForSingleObject(h_mutex2, -1);//P操作,/阻塞互斥对象mutex2,保证对R_Count的访问、修改互斥
      	R_Count++;//读者数目加1
      	if (R_Count == 1)// 如果当前只有一个读者
      	{
      		EnterCriticalSection(&WP_Write); // 则第一个读者负责上锁,不允许写操作
      	}
      	ReleaseMutex(h_mutex2); // 解锁,允许其他进程操作R_count
      	LeaveCriticalSection(&WP_Read); //让其他读者进入临界区
      	ReleaseMutex(h_mutex1);
      	printf("【Reader】 Thread 【%d】 begins 【reading】....\n", m_id);//读文件
      	Sleep(m_persist); // 模拟读文件延时
      	printf("【Reader】 Thread 【%d】 has 【finished】....\n", m_id); // 读文件结束
      	wait_for_mutex2 = WaitForSingleObject(h_mutex2, -1);//上锁,不允许其他进程操作R_count
      	R_Count--;// 读者数量减1
      	if (R_Count == 0) // 如果当前没有读者
      	{
      		LeaveCriticalSection(&WP_Write);//则可以开始写操作
      	}
      	ReleaseMutex(h_mutex2);// 解锁,允许其他进程操作R_count
      }
      
    • 写者线程

      void WP_WriterThread(void* p)
      {
      	DWORD wait_for_mutex3;
      	DWORD m_delay;//延迟时间
      	DWORD m_persist;//写文件持续时间
      	int m_id;//线程序号
      
      	HANDLE h_mutex3=OpenMutex(MUTEX_ALL_ACCESS, FALSE, "mutex3");
      
      	//从参数中获得信息
      	m_id = ((ThreadInfo*)(p))->id;
      	m_delay = (DWORD)(((ThreadInfo*)(p))->delay * INTE_PER_SEC);
      	m_persist = (DWORD)(((ThreadInfo*)(p))->persist * INTE_PER_SEC);
      	Sleep(m_delay);//延迟等待
      	printf("【Writer】 Thread 【%d】 is 【requesting】....\n", m_id);
      
      	wait_for_mutex3 = WaitForSingleObject(h_mutex3, -1);// 上锁,不允许其他进程操作R_count
      	W_Count++;//写者数量加1
      	if (W_Count == 1)// 如果当前只有一个写者
      	{
      		EnterCriticalSection(&WP_Read); // 则第一个读者负责上锁,不允许读操作
      	}
      	ReleaseMutex(h_mutex3);// 解锁,允许其他进程操作R_count
      
      	EnterCriticalSection(&WP_Write);// 上锁,不允许其他的进程写
      	printf("【Writer】 Thread 【%d】 beings 【writing】....\n", m_id);//写临界区
      	Sleep(m_persist);
      	printf("【Writer】 Thread 【%d】 has 【finished】....\n", m_id); // 写操作结束
      	LeaveCriticalSection(&WP_Write); //离开临界区 
      	wait_for_mutex3 = WaitForSingleObject(h_mutex3, -1);// 上锁,不允许其他进程操作R_count
      	W_Count--;//写者数量减1
      	if (W_Count == 0)// 如果当前没有写进程
      	{
      		LeaveCriticalSection(&WP_Read);// 则可以进行读操作
      	}
      	ReleaseMutex(h_mutex3); // 解锁,允许其他进程操作R_count
      }
      
    • 写者优先处理函数

      void WriterPriority(char* file)
      {
      	DWORD n_thread = 0;//线程数目
      	DWORD thread_ID;//线程ID
      	DWORD wait_for_all;//等待所有线程结束
      
      	//互斥对象
      	HANDLE h_Mutex1;
      	h_Mutex1 = CreateMutex(NULL, FALSE, "mutex1");
      	HANDLE h_Mutex2;
      	h_Mutex2 = CreateMutex(NULL, FALSE, "mutex2");
      	HANDLE h_Mutex3;
      	h_Mutex3 = CreateMutex(NULL, FALSE, "mutex3");
      
      	//线程对象
      	HANDLE h_Thread[MAX_THREAD_NUM];
      	ThreadInfo thread_info[MAX_THREAD_NUM];
      
      	R_Count = 0;//初始化R_Count
      	W_Count = 0;//初始化W_Count
      	InitializeCriticalSection(&WP_Write);//初始化临界区
      	InitializeCriticalSection(&WP_Read);
      	ifstream inFile;
      	inFile.open(file);//打开文件
      	printf("Reader Priority:\n\n");
      	while (inFile)
      	{
      		//读入每一个读者、写者的信息
      		inFile >> thread_info[n_thread].id;
      		inFile >> thread_info[n_thread].type;
      		inFile >> thread_info[n_thread].delay;
      		inFile >> thread_info[n_thread++].persist;
      		inFile.get();
      	}
      	for (int i = 0; i < (int)(n_thread); i++)
      	{
      		if (thread_info[i].type == READER || thread_info[i].type == 'r')
      		{
      			//创建读者进程
      			h_Thread[i] = CreateThread(NULL, 0,
      				(LPTHREAD_START_ROUTINE)(WP_ReaderThread),
      				&thread_info[i],
      				0, &thread_ID);
      		}
      		else {
      			//创建写者进程
      			h_Thread[i] = CreateThread(NULL, 0,
      				(LPTHREAD_START_ROUTINE)(WP_WriterThread),
      				&thread_info[i],
      				0, &thread_ID);
      		}
      	}
      	//等待所有线程结束
      	wait_for_all = WaitForMultipleObjects(n_thread, h_Thread, TRUE, -1);
      	printf("ALL DONE!\n");
      }
      
3.2.3 结果分析

运行环境:本地Windows电脑下的Visual Studio编译器

在这里插入图片描述

  • 读者优先

    由下图的结果可以看出,写者进程 4、2虽然都请求了写操作,但是却是等待所有的读者进程1、3、5读完了再按顺序进行的写入操作,符合读者优先的要求。

在这里插入图片描述

  • 写者优先

    如下图所示,虽然进程3、5都请求了写操作,但是等到写进程4、2、6全部完成写操作之后,最后才能完成线程3,5的读进程。满足了写者优先的要求。

在这里插入图片描述

3.3 哲学家就餐

3.3.1 问题描述
  • 问题背景

    一张圆桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子,桌子的中间是一碗米饭。哲学家们倾注毕生的精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子(一根一根地拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿起两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。

  • 避免死锁的方法

    • 可以对哲学家进程施加一些限制条件,比如最多允许四个哲学家同时进餐。这样可以保证至少有一个哲学家是可以拿到左右两只筷子的
    • 要求奇数号哲学家先拿左边的筷子,然后再拿右边的筷子,而偶数号哲学家刚好相反。用这种方法可以保证如果相邻的两个奇偶号哲学家都想吃饭,那么只会有其中一个可以拿起第一只筷子,另一个会直接阻塞。这就
      避免了占有一支后再等待另一只的情况。
    • 仅当一个哲学家左右两支筷子都可用时才允许他抓起筷子。
3.3.2 编程实现
3.3.2.1 解法1

哲学家进程分别拿起左边的筷子和右边的筷子,拿起每只筷子前都会对该筷子上锁,当拿起一双筷子后,哲学家就开始吃饭。吃完饭后,会释放筷子资源。但是该算法存在的问题是,有可能每个哲学家都拿起自己右边的筷子,导致所有的哲学家都不能吃饭,产生死锁现象。

关键代码如下:

int chopsticks[5]; // 访问5只筷子的互斥信号量
void philosopher(int i) {
	while (true) {
		think(i);// 哲学家处于思考状态
		Wait(chopsticks[i]);// 哲学家拿起左边的筷子
		printf("哲学家 %d 拿起左边的筷子\n", i);
		Wait(chopsticks[(i + 1) % 5]); // 哲学家拿起右边的筷子
		printf("哲学家 %d 拿起右边的筷子\n", i);
		eat(i); // 得到一双筷子后,哲学家开始吃饭
		Signal(chopsticks[i]);// 哲学家放下左边的筷子
		printf("哲学家 %d 放下左边的筷子\n", i);
		Signal(chopsticks[(i + 1) % 5]); // 哲学家放下右边的筷子
		printf("哲学家 %d 放下右边的筷子\n", i);
	}
}
3.3.2.2 解法2

思路:至多只允许四位哲学家同时去拿左筷子,最终能保证至少有一位哲学家能进餐,并在用完后释放两只筷子供他人使用。因此添加互斥信号量count=4, 表示最多只允许4个哲学家用餐。

关键代码如下:

int count;// 最多只允许4个哲学家进餐,count = IniSem(4); 
int chopsticks[5]; // 设置5只筷子的互斥信号量

void philosopher(int i) {
	while (true) {
		think(i);
		Wait(count); 
		printf("哲学家 %d 加入\n", i);
		Wait(chopsticks[i]);
		printf("哲学家 %d 拿起左边的筷子\n", i);
		Wait(chopsticks[(i + 1) % 5]);
		printf("哲学家 %d 拿起右边的筷子\n", i);
		eat(i);
		Signal(chopsticks[i]);
		printf("哲学家 %d 放下左边的筷子\n", i);
		Signal(chopsticks[(i + 1) % 5]);
		printf("哲学家 %d 放下右边的筷子\n", i);
		Signal(count);
		printf("哲学家 %d 离开\n", i);
	}
}
3.3.2.2 解法3

思路:规定奇数号的哲学家先拿起他左边的筷子,然后再去拿他右边的筷子;而偶数号的哲学家则先拿起他右边的筷子,然后再去拿他左边的筷子。按此规定,将是1、2号哲学家竞争1号筷子,3、4号哲学家竞争3号筷子。即五个哲学家都竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一个哲学家能获得两支筷子而进餐

核心代码如下:

int chopsticks[5]; // 设置访问5只筷子的互斥信号量

void philosopher(int i) {
	while (true) {
		think(i);// 哲学家思考
		if (i % 2 == 1) { // 如果是奇数,则先拿左边的筷子
			Wait(chopsticks[i]);
			printf("哲学家%d拿左边的筷子\n", i);
			Wait(chopsticks[(i + 1) % 5]);
			printf("哲学家%d拿右边的筷子\n", i);
			eat(i);
			Signal(chopsticks[i]);
			printf("哲学家%d放下左边的筷子\n", i);
			Signal(chopsticks[(i + 1) % 5]);
			printf("哲学家%d放下右边的筷子\n", i);
		} else {// 如果是偶数,则先拿右边的筷子
			Wait(chopsticks[(i + 1) % 5]);
			printf("哲学家%d拿右边的筷子\n", i);
			Wait(chopsticks[i]);
			printf("哲学家%d拿左边的筷子\n", i);
			eat(i);
			Signal(chopsticks[(i + 1) % 5]);
			printf("哲学家%d放下右边的筷子\n", i);
			Signal(chopsticks[i]);
			printf("哲学家%d放下左边的筷子\n", i);
		}
	}
}
3.3.2.2 解法4

仅当哲学家的左,右两只筷子均可用时,才允许他拿起筷子进餐。通过互斥信号量 mutex 对哲学家进餐之前取左侧和右侧筷子的操作进行保护,可以防止死锁的出现。(当第i个哲学家将左右筷子都拿到了才允许其他哲学家拿筷子)如:哲学家4在拿起左筷子与右筷子中间没有其他的哲学家拿任何一个筷子,虽然他们也饿了,虽然他们要拿的筷子可能并没有被占用,所以这个会造成资源(筷子)得不到有效的利用。

int chopsticks[5];// 访问5只筷子的互斥信号量
int takeMutex;// 保证一个哲学家可以同时拿两个筷子
void philosopher(int i) {
	while (true) {
		think(i);
		Wait(takeMutex); // 上锁,保证哲学家可以同时拿到左右两边的筷子
		Wait(chopsticks[i]);
		printf("哲学家%d拿左边的筷子\n\n", i);
		Wait(chopsticks[(i + 1) % 5]);
		printf("哲学家%d拿有右边的筷子\n\n", i);
		Signal(takeMutex); // 释放锁
		eat(i);
		Signal(chopsticks[i]);
		printf("哲学家%d放下左边的筷子\n", i);
		Signal(chopsticks[(i + 1) % 5]);
		printf("哲学家%d放下右边的筷子\n", i);
	}
}
3.3.3 结果分析

运行环境:64位的Utuntu虚拟机

  • 输入make
    在这里插入图片描述

  • 输入make basic./basic.out

在这里插入图片描述

模拟过程如下:

在这里插入图片描述

  • 输入make most4./most4.out

在这里插入图片描述

输出结果如下图,最多只能有4个哲学家加入拿筷子的队伍。

在这里插入图片描述

  • 输入make oddleft./oddleft.out

在这里插入图片描述

输出结果如下图,可以看到奇数的哲学家总是先拿起左边的筷子,而偶数的哲学家总是先拿起右边的筷子。
在这里插入图片描述

  • 输入make get2./get2.out

在这里插入图片描述

输出的结果如下图,当哲学家在拿筷子的过程中,需要把两个筷子都拿到手,在此期间不会有其他的哲学家再做拿筷子的动作,从而避免了死锁。

在这里插入图片描述

4. 心得体会

通过这次实验,更加深入的理解了操作系统的进程同步问题,尤其是一些经典的进程同步问题。由于系统中存在多个进程和有限的资源,就会出现同步问题。如果一个资源同时在多个进程之间共享,则可能导致数据不一致。同时还学习了操作系统中同步的需要、信号量的使用和临界区。最后,通过编写代码分别在Windows和Linux系统下实现了多线程的编程模型,模拟实现了操作系统的进程同步,非常有意思。

5. 源码附录

5.1 Product_Consumer.cpp

#include<iostream>
#include<mutex>
#include<chrono>
#include<thread>
using namespace std;

int n = 10;	// 单个缓存区大小
int in[4] = { 0,0,0,0 }, out[4] = { 0,0,0,0 }; // 生产指针,消费指针
int count[3] = { 0,0,0 }; // 分别对应三个缓冲区所存放的数据个数,初始时都为0
int buffer[3][10]; // 3个缓存区(分别是1,2,3)每个缓冲区可以存放10个数据
mutex mtx[3]; // 缓冲区互斥量
mutex display_mtx;	// 输出数据锁

/*
*   生产者函数
*/
void producer(int id)
{
    while(1){
        for (int i = 0; i < 3; i++) // 依次遍历三个缓冲区,看哪一个缓冲区满足写条件
            {
            if (mtx[i].try_lock()) // 如果可以对第i个缓冲区上锁
                {
                if (count[i]!=n) // 且第i个缓冲区不满
                    {
                    buffer[i][in[i]] = 1; // 第i个缓冲区写指针对应的位置写为1
                    display_mtx.lock(); // 输出前上锁
                    cout << "【Producer" << id << "】: is producing 【product" << in[i] <<"】at 【Buffer"<<i+1 <<"】"<< endl;
                    cout << "【Buffer"<<i+1 << "】"<<":";
                    for (int j = 0; j < n; j++)
                    {
                        cout << buffer[i][j] << " "; // 遍历输出第i个缓冲区的值
                    }
                    cout<<endl<<"---------------------------------------------------------------------"<<endl;
                    display_mtx.unlock();// 输出后解锁

                    in[i] = (in[i] + 1) % n; // 第i个缓冲区的写指针循环遍历
                    count[i]++; // 第i 个缓冲区的个数加1
                    mtx[i].unlock(); // 对第i个缓冲区解锁

                    this_thread::sleep_for(chrono::seconds(1)); // 延迟1s
                    break;
                    }
                else // 第i个缓冲区已满
                {
                    mtx[i].unlock(); // 对第i个缓冲区解锁
                }
                }
            }
    }
}
/**
*   消费者函数
*/
void consumer(int id)
{
    while(1){
        for (int i = 0; i < 3; i++) // 依次遍历三个缓冲区,看哪一个缓冲区满足读条件
            {
            if (mtx[i].try_lock()) // 试探第i个缓冲区,并上锁
                {
                if (count[i] != 0) // 如果第i个缓冲区不为空
                    {
                    buffer[i][out[i]] = 0; // 将都指针指向的位置写入0,即消费
                    display_mtx.lock(); // 写数据上锁
                    cout << "【Consumer" << id << "】: is consuming 【product" << out[i] <<"】from 【Buffer"<<i+1 <<"】"<< endl;
                    cout << "【Buffer"<<i+1 << "】" << ":";
                    for (int j = 0; j < n; j++)
                    {
                        cout << buffer[i][j] << " "; // 输出第i个缓冲区的值
                    }
                    cout<<endl<<"---------------------------------------------------------------------"<<endl;
                    display_mtx.unlock(); // 写数据解锁

                    out[i] = (out[i] + 1) % n; // 读指针后移
                    count[i]--;// 第i个缓冲区的个数减1
                    mtx[i].unlock(); // 缓冲区解锁

                    this_thread::sleep_for(chrono::seconds(5)); // 等待5s
                    break;
                    }
                else // 第i个缓冲区为空,则解锁
                {
                    mtx[i].unlock();
                }
                }
            }
    } ;
}

int main() {
    //定义生产者和消费者
    thread t1(producer, 1); 
    thread t2(consumer, 1); 
    thread t3(producer, 2);
    thread t4(consumer, 2);
    thread t5(producer, 3);
    thread t6(consumer, 3);
    thread t7(producer, 4);
    thread t8(consumer, 4);
	//线程开始工作
    t1.join();
    t2.join();
    t3.join();
    t4.join();
    t5.join();
    t6.join();
    t7.join();
    t8.join();

    return 0;
}

5.2 Read_Write.cpp

#include  "windows.h"
#include  <conio.h>
#include  <stdlib.h>
#include  <iostream>
#include  <fstream>
#include  <io.h>
#include  <string.h>
#include  <stdio.h>
using namespace std;

#define  READER  'R'  //读者
#define  WRITER  'W' //写者
#define  INTE_PER_SEC  1000 //每秒时钟中断数目
#define  MAX_THREAD_NUM  64//最大线程数目
#define  MAX_FILE_NUM  32//最大数据文件数目
#define  MAX_STR_LEN  32//字符串长度

int  R_Count = 0;//读者数目
int  W_Count = 0;//写者数目
CRITICAL_SECTION  RP_Write;//读者优先——写者互斥
CRITICAL_SECTION  WP_Write;// 写者优先——写者互斥
CRITICAL_SECTION  WP_Read;//写者优先——读者互斥
struct  ThreadInfo//线程结构
{
	int  id;//线程序号
	char  type;//线程类别(判断是读者线程还是写者线程)
	double  delay;//线程延迟
	double  persist;//线程读写操作持续时间
};

/*
* 
读者优先-读者线程
p:读者线程信息
*
*/

void RP_ReaderThread(void* p)
{

	//h_Mutex 保证对R_Count的操作互斥
	HANDLE h_Mutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "mutex_for_R_Count");

	DWORD wait_for_mutex;//等待互斥变量所有权
	DWORD m_delay;//延迟时间
	DWORD m_persist;//读文件持续时间
	int m_id;//线程序号

	//从参数中获得读者线程的信息
	m_id = ((ThreadInfo*)(p))->id;
	m_delay = (DWORD)(((ThreadInfo*)(p))->delay * INTE_PER_SEC);
	m_persist = (DWORD)(((ThreadInfo*)(p))->persist * INTE_PER_SEC);
	Sleep(m_delay);//延迟等待
	printf("【Reader】 thread 【%d】 is 【requesting】...\n", m_id);

	wait_for_mutex = WaitForSingleObject(h_Mutex, -1);//wait操作,保证对R_Count的操作互斥
	R_Count++;//读者数目增加1
	if (R_Count == 1) // 如果当前只有一个读者进程
	{
		EnterCriticalSection(&RP_Write); // 则该进程负责上锁,不允许写进程执行
	}
	ReleaseMutex(h_Mutex);// signal操作,释放锁,允许其他进程操作R_count
	printf("【Reader】 thread 【%d】 begins 【reading】...\n", m_id);
	
	Sleep(m_persist); // 模拟读文件延时
	printf("【Reader】 thread 【%d】has 【finished】\n", m_id);// 读取完成,线程退出

	wait_for_mutex = WaitForSingleObject(h_Mutex, -1);// 上锁,防止多个进程同时操作R_count
	R_Count--;// 读者数量减1
	if (R_Count == 0) // 如果当前读者进程数为0
	{
		LeaveCriticalSection(&RP_Write); // 则最后一个离开的进程负责解锁,此时允许写进程执行
	}
	ReleaseMutex(h_Mutex);// 释放锁,允许其他进程操作R_count
}

/*
读者优先 - 写者线程
p: 写者线程信息
*/
//

void RP_WriterThread(void* p)
{
	DWORD m_delay;//延迟时间
	DWORD m_persist;//写文件持续时间
	int m_id;//线程序号
	//从参数中获得信息
	m_id = ((ThreadInfo*)(p))->id;
	m_delay = (DWORD)(((ThreadInfo*)(p))->delay * INTE_PER_SEC);
	m_persist = (DWORD)(((ThreadInfo*)(p))->persist * INTE_PER_SEC);
	Sleep(m_delay);//延迟等待
	printf("【Write】 Thread 【%d】 is 【requesting】....\n", m_id);
	EnterCriticalSection(&RP_Write);// 上锁,此时不允许其他的读写操作
	printf("【Write】 Thread 【%d】begins 【writing】\n", m_id);//写文件(临界区)
	Sleep(m_persist); //模拟写文件延时
	printf("【Write】 Thread 【%d】 has 【finished】\n", m_id);//写操作结束,退出线程
	LeaveCriticalSection(&RP_Write);// 解锁,开放其他的读写操作 
}

/*
* 读者优先处理函数
* file:文件名
*/

void ReaderPriority(char* file)
{
	DWORD n_thread = 0;//线程数目
	DWORD thread_ID;//线程ID
	DWORD wait_for_all;//等待所有线程结束

	HANDLE h_Mutex=CreateMutex(NULL, FALSE, "mutex_for_R_Count");

	//线程对象的数组
	HANDLE h_Thread[MAX_THREAD_NUM];
	ThreadInfo thread_info[MAX_THREAD_NUM];

	R_Count = 0;//初始化R_Count
	InitializeCriticalSection(&RP_Write);//初始化临界区
	ifstream inFile;
	inFile.open(file);//打开文件
	printf("Reader Priority:\n\n");
	while (inFile)
	{
		//读入每一个读者、写者的信息
		inFile >> thread_info[n_thread].id;
		inFile >> thread_info[n_thread].type;
		inFile >> thread_info[n_thread].delay;
		inFile >> thread_info[n_thread++].persist;
		inFile.get();
	}
	for (int i = 0; i < (int)(n_thread); i++)
	{
		if (thread_info[i].type == READER || thread_info[i].type == 'r')
		{
			//创建读者进程
			h_Thread[i] = CreateThread(NULL, 0,
				(LPTHREAD_START_ROUTINE)(RP_ReaderThread),
				&thread_info[i],
				0, &thread_ID);
		}
		else 
		{
			//创建写者进程
			h_Thread[i] = CreateThread(NULL, 0,
				(LPTHREAD_START_ROUTINE)(RP_WriterThread),
				&thread_info[i],
				0, &thread_ID);
		}
	}
	//等待所有线程结束
	wait_for_all = WaitForMultipleObjects(n_thread, h_Thread, TRUE, -1);
	printf("All done!\n");
}

/*
写者优先--读者进程
p:读者线程信息
*/

void WP_ReaderThread(void* p)
{
	//互斥变量
	HANDLE h_mutex1=OpenMutex(MUTEX_ALL_ACCESS, FALSE, "mutex1");
	HANDLE h_mutex2= OpenMutex(MUTEX_ALL_ACCESS, FALSE, "mutex2");

	DWORD wait_for_mutex1;//等待互斥变量所有权
	DWORD wait_for_mutex2;
	DWORD m_delay;//延迟时间
	DWORD m_persist;//读文件持续时间
	int m_id;//线程序号
	//从参数中获得信息
	m_id = ((ThreadInfo*)(p))->id;
	m_delay = (DWORD)(((ThreadInfo*)(p))->delay * INTE_PER_SEC);
	m_persist = (DWORD)(((ThreadInfo*)(p))->persist * INTE_PER_SEC);
	Sleep(m_delay);//延迟等待

	printf("【Reader】 Thread 【%d】 is 【requesting】....\n", m_id);
	wait_for_mutex1 = WaitForSingleObject(h_mutex1, -1);//wait操作
	EnterCriticalSection(&WP_Read);//wait操作,进入读者临界区
	wait_for_mutex2 = WaitForSingleObject(h_mutex2, -1);//P操作,/阻塞互斥对象mutex2,保证对R_Count的访问、修改互斥
	R_Count++;//读者数目加1
	if (R_Count == 1)// 如果当前只有一个读者
	{
		EnterCriticalSection(&WP_Write); // 则第一个读者负责上锁,不允许写操作
	}
	ReleaseMutex(h_mutex2); // 解锁,允许其他进程操作R_count
	LeaveCriticalSection(&WP_Read); //让其他读者进入临界区
	ReleaseMutex(h_mutex1);
	printf("【Reader】 Thread 【%d】 begins 【reading】....\n", m_id);//读文件
	Sleep(m_persist); // 模拟读文件延时
	printf("【Reader】 Thread 【%d】 has 【finished】....\n", m_id); // 读文件结束
	wait_for_mutex2 = WaitForSingleObject(h_mutex2, -1);//上锁,不允许其他进程操作R_count
	R_Count--;// 读者数量减1
	if (R_Count == 0) // 如果当前没有读者
	{
		LeaveCriticalSection(&WP_Write);//则可以开始写操作
	}
	ReleaseMutex(h_mutex2);// 解锁,允许其他进程操作R_count
}

/*
写者优先--写者线程
p:写者线程信息
*/

void WP_WriterThread(void* p)
{
	DWORD wait_for_mutex3;
	DWORD m_delay;//延迟时间
	DWORD m_persist;//写文件持续时间
	int m_id;//线程序号

	HANDLE h_mutex3=OpenMutex(MUTEX_ALL_ACCESS, FALSE, "mutex3");

	//从参数中获得信息
	m_id = ((ThreadInfo*)(p))->id;
	m_delay = (DWORD)(((ThreadInfo*)(p))->delay * INTE_PER_SEC);
	m_persist = (DWORD)(((ThreadInfo*)(p))->persist * INTE_PER_SEC);
	Sleep(m_delay);//延迟等待
	printf("【Writer】 Thread 【%d】 is 【requesting】....\n", m_id);

	wait_for_mutex3 = WaitForSingleObject(h_mutex3, -1);// 上锁,不允许其他进程操作R_count
	W_Count++;//写者数量加1
	if (W_Count == 1)// 如果当前只有一个写者
	{
		EnterCriticalSection(&WP_Read); // 则第一个读者负责上锁,不允许读操作
	}
	ReleaseMutex(h_mutex3);// 解锁,允许其他进程操作R_count

	EnterCriticalSection(&WP_Write);// 上锁,不允许其他的进程写
	printf("【Writer】 Thread 【%d】 beings 【writing】....\n", m_id);//写临界区
	Sleep(m_persist);
	printf("【Writer】 Thread 【%d】 has 【finished】....\n", m_id); // 写操作结束
	LeaveCriticalSection(&WP_Write); //离开临界区 
	wait_for_mutex3 = WaitForSingleObject(h_mutex3, -1);// 上锁,不允许其他进程操作R_count
	W_Count--;//写者数量减1
	if (W_Count == 0)// 如果当前没有写进程
	{
		LeaveCriticalSection(&WP_Read);// 则可以进行读操作
	}
	ReleaseMutex(h_mutex3); // 解锁,允许其他进程操作R_count
}

/*
写者优先处理函数
file:文件名
*/


void WriterPriority(char* file)
{
	DWORD n_thread = 0;//线程数目
	DWORD thread_ID;//线程ID
	DWORD wait_for_all;//等待所有线程结束

	//互斥对象
	HANDLE h_Mutex1;
	h_Mutex1 = CreateMutex(NULL, FALSE, "mutex1");
	HANDLE h_Mutex2;
	h_Mutex2 = CreateMutex(NULL, FALSE, "mutex2");
	HANDLE h_Mutex3;
	h_Mutex3 = CreateMutex(NULL, FALSE, "mutex3");

	//线程对象
	HANDLE h_Thread[MAX_THREAD_NUM];
	ThreadInfo thread_info[MAX_THREAD_NUM];

	R_Count = 0;//初始化R_Count
	W_Count = 0;//初始化W_Count
	InitializeCriticalSection(&WP_Write);//初始化临界区
	InitializeCriticalSection(&WP_Read);
	ifstream inFile;
	inFile.open(file);//打开文件
	printf("Writer Priority:\n\n");
	while (inFile)
	{
		//读入每一个读者、写者的信息
		inFile >> thread_info[n_thread].id;
		inFile >> thread_info[n_thread].type;
		inFile >> thread_info[n_thread].delay;
		inFile >> thread_info[n_thread++].persist;
		inFile.get();
	}
	for (int i = 0; i < (int)(n_thread); i++)
	{
		if (thread_info[i].type == READER || thread_info[i].type == 'r')
		{
			//创建读者进程
			h_Thread[i] = CreateThread(NULL, 0,
				(LPTHREAD_START_ROUTINE)(WP_ReaderThread),
				&thread_info[i],
				0, &thread_ID);
		}
		else {
			//创建写者进程
			h_Thread[i] = CreateThread(NULL, 0,
				(LPTHREAD_START_ROUTINE)(WP_WriterThread),
				&thread_info[i],
				0, &thread_ID);
		}
	}
	//等待所有线程结束
	wait_for_all = WaitForMultipleObjects(n_thread, h_Thread, TRUE, -1);
	printf("ALL DONE!\n");
}

int main(int argc, char* argv[])
{
	printf("----------------------Lab6: 读者写者问题--------------------------\n");
	printf("Name: ZYW | Date: 2022/3/29\n");
	printf("------------------------------------------------------------------\n");
	char ch;
	char str[50] = ".\\thread.dat";//最好输入数据的绝对路径;
	while (1)
	{
		//打印提示信息
		printf("---------------MENU---------------\n");
		printf("         R:Reader Priority\n");
		printf("         W:Writer Priority\n");
		printf("         E:Exit \n");
		printf("----------------------------------\n");
		printf("Please Enter R or W or E:");
		//如果信息不正确,继续输入
		ch = (char)_getch();
		while (ch != 'R' && ch != 'W' && ch != 'E')
		{
			ch = (char)_getch();
		}

		system("cls");
		//选择E,返回
		if (ch == 'E')
			return 0;
		//选择R,读者优先
		else if (ch == 'R')
			ReaderPriority(str);
		//选择W,写者优先
		else if(ch=='W')
			WriterPriority(str);
		//结束
		printf("\n Please Enter any Keys to Exit:");
		_getch();
		system("cls");
	}
	return 0;
}

5.3 Solution1_basic.cpp

/*
basic.cpp
最基本的解决方案,但是可能导致死锁
*/
#include "semaphore.h"
#include "philosopher.h"
#include <unistd.h>
#include <sys/types.h>

void philosopher(int i); // 哲学家进程
int chopsticks[5]; // 访问5只筷子的互斥信号量

int main(int argc, char * args[]) {
	for (int i = 0; i < 5; ++i)
		chopsticks[i] = IniSem(1); // 初始化互斥信号量
	int fpid = 1;
	int amount = 5;
	while (fpid != 0 && --amount) // 创建5个进程
		fpid = fork();
	philosopher(amount);
	return 0;
}

void philosopher(int i) {
	while (true) {
		think(i);// 哲学家处于思考状态
		Wait(chopsticks[i]);// 哲学家拿起左边的筷子
		printf("哲学家 %d 拿起左边的筷子\n", i);
		Wait(chopsticks[(i + 1) % 5]); // 哲学家拿起右边的筷子
		printf("哲学家 %d 拿起右边的筷子\n", i);
		eat(i); // 得到一双筷子后,哲学家开始吃饭
		Signal(chopsticks[i]);// 哲学家放下左边的筷子
		printf("哲学家 %d 放下左边的筷子\n", i);
		Signal(chopsticks[(i + 1) % 5]); // 哲学家放下右边的筷子
		printf("哲学家 %d 放下右边的筷子\n", i);
	}
}

5.3 Solution2_most4.cpp

#include "semaphore.h"
#include "philosopher.h"
#include <unistd.h>
#include <sys/types.h>
void philosopher(int i); // 哲学家进程

int count;// 最多只允许4个哲学家进餐
int chopsticks[5]; // 设置5只筷子的互斥信号量

int main(int argc, char * args[]) {
	for (int i = 0; i < 5; ++i)
		chopsticks[i] = IniSem(1); // 初始化5只筷子的互斥信号量
	count = IniSem(4); // 最多只允许4个哲学家用餐
	int fpid = 1;
	int amount = 5;
	while (fpid != 0 && --amount)
		fpid = fork();// 创建5个进程
	philosopher(amount);
	return 0;
}

void philosopher(int i) {
	while (true) {
		think(i);
		Wait(count); 
		printf("哲学家 %d 加入\n", i);
		Wait(chopsticks[i]);
		printf("哲学家 %d 拿起左边的筷子\n", i);
		Wait(chopsticks[(i + 1) % 5]);
		printf("哲学家 %d 拿起右边的筷子\n", i);
		eat(i);
		Signal(chopsticks[i]);
		printf("哲学家 %d 放下左边的筷子\n", i);
		Signal(chopsticks[(i + 1) % 5]);
		printf("哲学家 %d 放下右边的筷子\n", i);
		Signal(count);
		printf("哲学家 %d 离开\n", i);
	}
}

5.4 Solution3_oddleft.cpp

#include "semaphore.h"
#include "philosopher.h"
#include <unistd.h>
#include <sys/types.h>

void philosopher(int i);
int chopsticks[5]; // 设置访问5只筷子的互斥信号量

int main(int argc, char * args[]) {
	for (int i = 0; i < 5; ++i)
		chopsticks[i] = IniSem(1);// 初始化5只筷子的互斥信号量
	int fpid = 1; 
	int amount = 5;
	while (fpid != 0 && --amount) // 创建5个互斥进程
		fpid = fork();
	philosopher(amount);
	return 0;
}

void philosopher(int i) {
	while (true) {
		think(i);// 哲学家思考
		if (i % 2 == 1) { // 如果是奇数,则先拿左边的筷子
			Wait(chopsticks[i]);
			printf("哲学家%d拿左边的筷子\n", i);
			Wait(chopsticks[(i + 1) % 5]);
			printf("哲学家%d拿右边的筷子\n", i);
			eat(i);
			Signal(chopsticks[i]);
			printf("哲学家%d放下左边的筷子\n", i);
			Signal(chopsticks[(i + 1) % 5]);
			printf("哲学家%d放下右边的筷子\n", i);
		} else {// 如果是偶数,则先拿右边的筷子
			Wait(chopsticks[(i + 1) % 5]);
			printf("哲学家%d拿右边的筷子\n", i);
			Wait(chopsticks[i]);
			printf("哲学家%d拿左边的筷子\n", i);
			eat(i);
			Signal(chopsticks[(i + 1) % 5]);
			printf("哲学家%d放下右边的筷子\n", i);
			Signal(chopsticks[i]);
			printf("哲学家%d放下左边的筷子\n", i);
		}
	}
}

5.5 Solution4_get2.cpp

#include "semaphore.h"
#include "philosopher.h"
#include <unistd.h>
#include <sys/types.h>

void philosopher(int i);
int chopsticks[5];// 访问5只筷子的互斥信号量
int takeMutex;// 保证一个哲学家可以同时拿两个筷子

int main(int argc, char * args[]) {
	for (int i = 0; i < 5; ++i)
		chopsticks[i] = IniSem(1); 
	takeMutex = IniSem(1);
	int fpid = 1;
	int amount = 5;
	while (fpid != 0 && --amount)
		fpid = fork();  // 创建5个哲学家进程
	philosopher(amount);
	return 0;
}

void philosopher(int i) {
	while (true) {
		think(i);
		Wait(takeMutex); // 上锁,保证哲学家可以同时拿到左右两边的筷子
		Wait(chopsticks[i]);
		printf("哲学家%d拿左边的筷子\n\n", i);
		Wait(chopsticks[(i + 1) % 5]);
		printf("哲学家%d拿有右边的筷子\n\n", i);
		Signal(takeMutex); // 释放锁
		eat(i);
		Signal(chopsticks[i]);
		printf("哲学家%d放下左边的筷子\n", i);
		Signal(chopsticks[(i + 1) % 5]);
		printf("哲学家%d放下右边的筷子\n", i);
	}
}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐