一、实验目的
1.生产者消费者问题:
①实现生产者—消费者问题的模拟,以便更好的理解此经典进程同步问题。生产者-消费者问题是典型的PV操作问题,假设系统中有一个比较大的缓冲池,生产者的任务是只要缓冲池未满就可以将生产出的产品放入其中,而消费者的任务是只要缓冲池未空就可以从缓冲池中拿走产品。缓冲池被占用时,任何进程都不能访问。
②每一个生产者都要把自己生产的产品放入缓冲池,每个消费者从缓冲池中取走产品消费。在这种情况下,生产者消费者进程同步,因为只有通过互通消息才知道是否能存入产品或者取走产品。他们之间也存在互斥,即生产者消费者必须互斥访问缓冲池,即不能有两个以上的进程同时进行。
2.读者-写者问题:
创建一个控制台进程,此进程包含n个线程。用这n个线程来表示n个读者或写者。每个线程按照设定进行读写操作。用信号量机制分别实现读者优先和写者优先的读者写者问题。 读者和写者问题的读写操作限制(包括读者优先和写者优先): 写写互斥:即不能有两个写者同时进行写操作。 读写互斥:即不能同时有一个线程在读,而另一个线程在写。 读读允许:即可以有一个或多个读者在读。 读者优先的附加限制:如果一个读者申请进行读操作时已有另一个读者正在进行读操作,则该读者可直接开始读操作。 写者优先的附加限制:如果一个读者申请进行读操作时已有另一写者在等待访问共享资源,则该读者必须等到没有写者处于等待状态才能开始读操作。
二、实验过程
生产者-消费者问题
问题描述
用信号量模拟生产者-消费者问题的过程。生产者和消费者两个线程共享同一个缓冲区,生产者不断向缓冲区中添加产品,消费者从缓冲区中消费产品。要保证缓冲区满了之后生产者不能再继续添加产品,需要等消费者至少消费一个产品之后生产者才能继续生产产品;缓冲区空了之后消费者不能再消费产品,需要等生产者至少生产一个产品之后消费者才能继续消费产品。
向缓冲区中添加产品和从缓冲区消费产品需要互斥操作保持同步。
生产者需要等待缓冲区有空间才能生产产品;消费者也不能在缓冲区为空时消费产品。这两个过程需要信号量来通知进行。
完整代码
#include <stdio.h> #include <pthread.h> #include <windows.h> #define N 100 #define true 1 #define producerNum 10 #define consumerNum 5 #define sleepTime 1000
typedef int semaphore; typedef int item; item buffer[N] = {0}; int in = 0; int out = 0; int proCount = 0; semaphore mutex = 1, empty = N, full = 0, proCmutex = 1;
void * producer(void * a){ while(true){ while(proCmutex <= 0); proCmutex--; proCount++; printf("生产一个产品ID%d, 缓冲区位置为%d\n",proCount,in); proCmutex++;
while(empty <= 0){ printf("缓冲区已满!\n"); } empty--; while(mutex <= 0); mutex--; buffer[in] = proCount; in = (in + 1) % N; mutex++; full++; Sleep(sleepTime); }
}
void * consumer(void *b){ while(true){ while(full <= 0){ printf("缓冲区为空!\n"); } full--;
while(mutex <= 0); mutex--; int nextc = buffer[out]; buffer[out] = 0;//消费完将缓冲区设置为0 out = (out + 1) % N; mutex++; empty++; printf("\t\t\t\t消费一个产品ID%d,缓冲区位置为%d\n", nextc,out); Sleep(sleepTime); }
}
int main() { pthread_t threadPool[producerNum+consumerNum]; int i; for(i = 0; i < producerNum; i++){ pthread_t temp; if(pthread_create(&temp, NULL, producer, NULL) == -1){ printf("ERROR, fail to create producer%d\n", i); exit(1); } threadPool[i] = temp; }//创建生产者进程放入线程池
for(i = 0; i < consumerNum; i++){ pthread_t temp; if(pthread_create(&temp, NULL, consumer, NULL) == -1){ printf("ERROR, fail to create consumer%d\n", i); exit(1); } threadPool[i+producerNum] = temp; }//创建消费者进程放入线程池
void * result; for(i = 0; i < producerNum+consumerNum; i++){ if(pthread_join(threadPool[i], &result) == -1){ printf("fail to recollect\n"); exit(1); } }//运行线程池 return 0; }
读者-写者问题
代码:
#include<iostream> #include<string> #include <conio.h> #include <stdlib.h> #include <stdio.h> #include <fstream> #include <io.h> #include <string.h> #include<algorithm> #include<Windows.h> //多线程编程 #include<process.h> using namespace std;
#define READER 'R' //读者 #define WRITER 'W' //写者 #define INTE_PER_SEC 1000 //每秒时钟中断的数目 #define MAX_THREAD_NUM 64 //最大线程数目
//变量声明初始化 int readercount = 0;//记录等待的读者数目 int writercount = 0;//记录等待的写者数目
HANDLE rc_mutex;//因为读者数量而添加的互斥信号量,用于读者优先
HANDLE rc2_mutex;//因为读者数量而添加的互斥信号量,用于写者优先 HANDLE wc_mutex;//因为写者数量而添加的互斥信号量 HANDLE book;//互斥访问信号量 HANDLE wrt;//保证每次只有一个写者进行写操作,当写者的数量writercount等于0的时候,则证明此时没有没有读者了,释放信号量book HANDLE mutex;//避免写者同时与多个读者进行竞争,读者中信号量RWMutex比mutex3先释放,则一旦有写者,写者可马上获得资源
struct thread_info { int id; //线程序号 char entity; //线程类别(判断是读者线程还是写者线程) double delay; //线程延迟时间 double lastTime; //线程读写操作时间 }; /*****/ //读者优先 //进程管理-读者线程 void rp_threadReader(void p) { DWORD m_delay; //延迟时间 DWORD m_persist; //读文件持续时间 int m_serial; //线程序号 //从参数中获得信息 m_serial = ((thread_info)(p))->id; m_delay = (DWORD)(((thread_info*)(p))->delay INTE_PER_SEC); m_persist = (DWORD)(((thread_info)(p))->lastTime *INTE_PER_SEC); Sleep(m_delay); //延迟等待
printf("读者进程%d申请读文件.\n", m_serial); //cout << "读者进程"<< m_serial<<"申请读文件." << endl; WaitForSingleObject(rc_mutex, -1);//对readercount互斥访问 if (readercount == 0)WaitForSingleObject(book, -1);//第一位读者申请书 readercount++; ReleaseSemaphore(rc_mutex, 1, NULL);//释放互斥信号量rc_mutex printf("读者进程%d开始读文件.\n", m_serial); Sleep(m_persist); printf("读者进程%d完成读文件.\n", m_serial); WaitForSingleObject(rc_mutex, -1);//修改readercount readercount--;//读者读完 if (readercount == 0)ReleaseSemaphore(book, 1, NULL);//释放书籍,写者可写 ReleaseSemaphore(rc_mutex, 1, NULL);//释放互斥信号量rc_mutex
} /*****/ //读者优先 //进程管理-写者线程 void rp_threadWriter(void p) { DWORD m_delay; //延迟时间 DWORD m_persist; //读文件持续时间 int m_serial; //线程序号 //从参数中获得信息 m_serial = ((thread_info)(p))->id; m_delay = (DWORD)(((thread_info*)(p))->delay INTE_PER_SEC); m_persist = (DWORD)(((thread_info)(p))->lastTime INTE_PER_SEC); Sleep(m_delay); //延迟等待 printf("写者进程%d申请写文件.\n", m_serial); WaitForSingleObject(book, INFINITE);//申请资源 /write is performed*/ printf("写者进程%d开始读文件.\n", m_serial); Sleep(m_persist); printf("写者进程%d完成读文件.\n", m_serial); ReleaseSemaphore(book, 1, NULL);//释放资源 } //读者优先 void ReaderPriority(char *file) { DWORD n_thread = 0; //线程数目 DWORD thread_ID; //线程ID DWORD wait_for_all; //等待所有线程结束
//创建信号量 rc_mutex = CreateSemaphore(NULL, 1, 1, "mutex_for_readcount");//读者对count修改互斥信号量,初值为1,最大为1 book = CreateSemaphore(NULL, 1, 1, NULL);//书籍互斥访问信号量,初值为1,最大值为1 HANDLE h_Thread[MAX_THREAD_NUM];//线程句柄,线程对象的数组 thread_info thread_info[MAX_THREAD_NUM]; int id = 0; readercount = 0; //初始化readcount ifstream inFile; inFile.open(file); cout << "读者优先:" << endl; while (inFile) { //读入每一个读者,写者的信息 inFile >> thread_info[n_thread].id; inFile >> thread_info[n_thread].entity; inFile >> thread_info[n_thread].delay; inFile >> thread_info[n_thread++].lastTime; inFile.get(); } for (int i = 0; i<(int)(n_thread); i++) { if (thread_info[i].entity == READER || thread_info[i].entity == 'r') { //创建读者进程 h_Thread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(rp_threadReader), &thread_info[i], 0, &thread_ID); } else { //创建写线程 h_Thread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(rp_threadWriter), &thread_info[i], 0, &thread_ID); } } //等待子线程结束 //关闭句柄 wait_for_all = WaitForMultipleObjects(n_thread, h_Thread, TRUE, -1); cout << endl; cout << "所有读者写者已经完成操作!!" << endl; for(int i = 0; i<(int)(n_thread); i++) CloseHandle(h_Thread[i]); CloseHandle(rc_mutex); CloseHandle(book);
} /*****/ //写者优先 //进程管理-读者线程 void wp_threadReader(void p) { DWORD m_delay; //延迟时间 DWORD m_persist; //读文件持续时间 int m_serial; //线程序号 //从参数中获得信息 m_serial = ((thread_info)(p))->id; m_delay = (DWORD)(((thread_info*)(p))->delay INTE_PER_SEC); m_persist = (DWORD)(((thread_info)(p))->lastTime *INTE_PER_SEC); Sleep(m_delay); //延迟等待
printf("读者进程%d申请读文件.\n", m_serial); WaitForSingleObject(mutex, -1); WaitForSingleObject(book, -1); WaitForSingleObject(rc2_mutex, -1);//对readercount互斥访问 if (readercount == 0)WaitForSingleObject(wrt, -1);//第一位读者申请书,同时防止写者进行写操作 readercount++; ReleaseSemaphore(rc2_mutex, 1, NULL);//释放互斥信号量rc_mutex ReleaseSemaphore(book, 1, NULL);//释放互斥信号量book ReleaseSemaphore(mutex, 1, NULL);//释放互斥信号量mutex /* reading is performed */ printf("读者进程%d开始读文件.\n", m_serial); Sleep(m_persist); printf("读者进程%d完成读文件.\n", m_serial); WaitForSingleObject(rc2_mutex, -1);//修改readercount readercount--;//读者读完 if (readercount == 0)ReleaseSemaphore(wrt, 1, NULL);//释放资源,写者可写 ReleaseSemaphore(rc2_mutex, 1, NULL);//释放互斥信号量rc_mutex
} /*****/ //写者优先 //进程管理-写者线程 void wp_threadWriter(void p) { DWORD m_delay; //延迟时间 DWORD m_persist; //读文件持续时间 int m_serial; //线程序号 //从参数中获得信息 m_serial = ((thread_info)(p))->id; m_delay = (DWORD)(((thread_info*)(p))->delay INTE_PER_SEC); m_persist = (DWORD)(((thread_info)(p))->lastTime *INTE_PER_SEC); Sleep(m_delay); //延迟等待
printf("写者进程%d申请写文件.\n", m_serial); WaitForSingleObject(wc_mutex, -1);//对writercount互斥访问 if (writercount == 0)WaitForSingleObject(book, -1);//第一位写者申请资源 writercount++; ReleaseSemaphore(wc_mutex, 1, NULL);//释放资源 WaitForSingleObject(wrt, -1); /*write is performed*/ printf("写者进程%d开始写文件.\n", m_serial); Sleep(m_persist); printf("写者进程%d完成写文件.\n", m_serial); ReleaseSemaphore(wrt, 1, NULL);//释放资源 WaitForSingleObject(wc_mutex, -1);//对writercount互斥访问 writercount--; if (writercount == 0)ReleaseSemaphore(book, 1, NULL);//释放资源 ReleaseSemaphore(wc_mutex, 1, NULL);//释放资源
} //写者优先 void WriterPriority(char *file) { DWORD n_thread = 0; //线程数目 DWORD thread_ID; //线程ID DWORD wait_for_all; //等待所有线程结束
//创建信号量 rc2_mutex = CreateSemaphore(NULL, 1, 1, "mutex_for_readercount");//读者对count修改互斥信号量,初值为1,最大为1 wc_mutex = CreateSemaphore(NULL, 1, 1, "mutex_for_writercount");//写者对count修改互斥信号量,初值为1,最大为1 wrt = CreateSemaphore(NULL, 1, 1, NULL);// mutex = CreateSemaphore(NULL, 1, 1, NULL);// book = CreateSemaphore(NULL, 1, 1, NULL);//书籍互斥访问信号量,初值为1,最大值为1 HANDLE h_Thread[MAX_THREAD_NUM];//线程句柄,线程对象的数组 thread_info thread_info[MAX_THREAD_NUM]; int id = 0; readercount = 0; //初始化readcount writercount = 0; ifstream inFile; inFile.open(file); cout << "写者优先:" << endl; while (inFile) { //读入每一个读者,写者的信息 inFile >> thread_info[n_thread].id; inFile >> thread_info[n_thread].entity; inFile >> thread_info[n_thread].delay; inFile >> thread_info[n_thread++].lastTime; inFile.get(); } for (int i = 0; i<(int)(n_thread); i++) { if (thread_info[i].entity == READER || thread_info[i].entity == 'r') { //创建读者进程 h_Thread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(wp_threadReader), &thread_info[i], 0, &thread_ID); } else { //创建写线程 h_Thread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(wp_threadWriter), &thread_info[i], 0, &thread_ID); } } //等待子线程结束 //关闭句柄 wait_for_all = WaitForMultipleObjects(n_thread, h_Thread, TRUE, -1); cout << endl; cout << "所有读者写者已经完成操作!!" << endl; for (int i = 0; i<(int)(n_thread); i++) CloseHandle(h_Thread[i]); CloseHandle(wc_mutex); CloseHandle(rc2_mutex); CloseHandle(book);
} //主函数 int main() { char choice; cout << " 欢迎进入读者写者模拟程序 " << endl; while (true) { //打印提示信息 cout << " 请输入你的选择 " << endl; cout << " 1、读者优先" << endl; cout << " 2、写者优先" << endl; cout << " 3、退出程序" << endl; cout << endl; //如果输入信息不正确,继续输入 do { choice = (char)_getch(); } while (choice != '1'&&choice != '2'&&choice != '3');
system("cls"); //选择1,读者优先 if (choice == '1') //ReaderPriority("thread.txt"); ReaderPriority(const_cast<char *>("thread.txt")); //选择2,写者优先 else if (choice == '2') WriterPriority(const_cast<char *>("thread.txt")); //选择3,退出 else return 0; //结束 printf("\nPress Any Key to Coutinue"); _getch(); system("cls"); } return 0; }
三、实验结果
生产者-消费者问题
读者-写者问题
测试文本
四、实验心得
在实验中,我使用了记录型信号量来实现读写问题和生产者消费者问题。通过这次实验,我深刻体会到记录型信号量在多线程编程中的重要性和灵活性。
在读写问题中,我使用了两个记录型信号量来实现对共享资源的读写操作。当有读操作时,我使用一个记录型信号量来控制读者数量,保证在有写操作时读者无法同时访问共享资源;当有写操作时,我使用另一个记录型信号量来保证只有一个写者能够访问共享资源,避免读写冲突。通过这种方式,我成功地解决了读写问题,确保了读写操作的正确性和一致性。
在生产者消费者问题中,我同样使用了记录型信号量来实现生产者和消费者之间的同步。我使用了两个记录型信号量来控制生产者和消费者的进程数量,确保生产者和消费者能够交替执行,避免了生产者和消费者之间的竞争和死锁。通过这种方式,我成功地解决了生产者消费者问题,确保了生产者和消费者之间的协作和同步。