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

code link: 欢迎star

1. 实验目的

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

2. 实验内容

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

3. 实验要求

典型同步问题模拟处理编程功能设计要求为:运用 Windows(或 Linux)操作系统同步机制,从生产者-消费者问题、读者优先的读者-写者问题、写者优先的读者-写者问题、读者数限定的读者-写者问题、哲学家就餐问题等典型同步问题中选取四个同步问题,分析、设计和编程模拟实现相应问题的解决方案。

4. 实验思路

  1. 第一部分,生成者消费者问题。使用两个整型信号量empty和full,一个互斥控制量mutex。创建两个线程函数,read和write。这里参考了ppt上的代码,采用了一个nloop进行循环次数控制。
  2. 第二部分,读者优先的读者和写者问题。其中,读者可以并发执行,但是写者进程与其他进程互斥。Readcount计数变量也是临界资源,也要添加一个互斥变量,进行互斥访问。由于读写操作我们可以简化为往流中读写数据,我们可以直接使用一个string 的buffer进行读写,相比文件的读写更加快速,适用于实验的调试。
  3. 第三部分,写者优先的读者和写者问题。我们的目标是,写者优先,即需要有一个写者队列,注意写者仍要保持互斥,只要队列数大于0读者就不能进行;同时,读者要能进行并行读出且写者仍要互斥。相比第二部分,只要增加一个由写者队列数控制的开关即可。
  4. 第四部分,哲学家就餐问题。这里采用互斥信号量保证同时拿起两支筷子。同时设置一个最高nloop控制最多吃饭次数;将think和eat简化为交互内容输出,方便程序验证。

5. 实验内容

5.1. 相关环境介绍

操作系统:window 10 21H2
开发环境:Clion-2022.2.1-Windows
编译器:mwing-10.0

5.2. 生产者消费者问题

代码:

01:#define N 5  
02:#define MAXNLOOP 30  
03:  
04:int in = 0;  
05:int out = 0;  
06:HANDLE WINAPI empty = CreateSemaphore(NULL, 10, 10, "empty");  
07:HANDLE WINAPI full = CreateSemaphore(NULL, 0, 10, "full");  
08:HANDLE WINAPI mutex = CreateMutex(NULL, FALSE, "MutexToProtectCriticalResource");  
09:  
10:string buffer[N];  
11:int nloop = 0;  
12:DWORD WINAPI ThreadExecuteZZWProducer(LPVOID lpParameter){  
13:    int *pID = (int *)lpParameter;  
14:    string nextp = to_string(*pID) + " produced";  
15:    while(1){  
16:        WaitForSingleObject(empty, INFINITE);  
17:        WaitForSingleObject(mutex, INFINITE);  
18:        if(nloop>MAXNLOOP) break;  
19:        buffer[in] = nextp;  
20:        in = (in + 1) % N;  
21:        cout << "nloop " << nloop << " producer " <<  *pID << " has produced on the buffer " << in << "\n";  
22:        ++nloop;  
23:        ReleaseMutex(mutex);  
24:        ReleaseSemaphore(full, 1, NULL);  
25:  
26:    }  
27:    return 0;  
28:}  
29:  
30:DWORD WINAPI ThreadExecuteZZWConsumer(LPVOID lpParameter){  
31:    int *pID = (int*)lpParameter;  
32:    string nextp;  
33:    while(1){  
34:        WaitForSingleObject(full, INFINITE);  
35:        WaitForSingleObject(mutex, INFINITE);  
36:        if(nloop>MAXNLOOP) break;  
37:        nextp = buffer[out];  
38:        out = (out +1) % N;  
39:        cout << "nloop " << nloop << " consumer "<< *pID << " has consumed on the buffer " << out << " the content is producer "<< nextp << "\n";  
40:        ++nloop;  
41:        ReleaseMutex(mutex);  
42:        ReleaseSemaphore(empty, 1, NULL);  
43:    };  
44:    return 0;  
45:}  

结果验证
生产者消费者问题的验证

结果符合预期,四个线程都可以参与读写,读出的内容经人工验证也是正确的。只不过可能考虑到运行速度过快,出现过buffer数太多时有些buffer永远用不到的情况,最后我们选择了5个buffer进行实验。

5.3. 读者优先的读者-写者问题

核心代码:

46:#define N 1  
47:#define MAXNLOOP 30  
48:int readercount =0;  
49:HANDLE WINAPI rmutex = CreateMutex(NULL, FALSE, "MutexToProtectCriticalResource");  
50:HANDLE WINAPI wmutex = CreateMutex(NULL, FALSE, "MutexToProtectCriticalResource");  
51:  
52:string buffer[N];  
53:int nloop = 0;  
54:int in = 0;  
55:int out = 0;  
56:DWORD WINAPI ThreadExecuteZZWWriter(LPVOID lpParameter){  
57:    int *pID = (int*)lpParameter;  
58:    string nextp = to_string(*pID) + " wrote";  
59:  
60:    while(1){  
61:        WaitForSingleObject(wmutex, INFINITE);  
62:        if(nloop >= MAXNLOOP) break;  
63:        buffer[in] = nextp;  
64:        in = (in + 1) % N;  
65:        cout << "nloop " << nloop << " writer " <<  *pID << " has wrote on the buffer " << in << "\n";  
66:        ++nloop;  
67:        ReleaseMutex(wmutex);  
68:    }  
69:    return 0;  
70:}  
71:  
72:DWORD WINAPI ThreadExecuteZZWReader(LPVOID lpParameter){  
73:    int *pID = (int*)lpParameter;  
74:    string nextp;  
75:    while(1){  
76:        WaitForSingleObject(rmutex, INFINITE);  
77:        if(readercount == 0) WaitForSingleObject(wmutex, INFINITE);  
78:        ++readercount;  
79:        if(nloop>=MAXNLOOP) break;  
80:        ++nloop;  
81:        ReleaseMutex(rmutex);  
82:  
83:        nextp = buffer[out];  
84:        out = (out +1) % N;  
85:        cout << "nloop " << nloop << " reader "<< *pID << " has read on the buffer " << out << " the content is writer "<< nextp << "\n";  
86:  
87:        WaitForSingleObject(rmutex, INFINITE);  
88:        --readercount;  
89:        if(readercount==0) ReleaseMutex(wmutex);  
90:        ReleaseMutex(rmutex);  
91:    };  
92:    return 0;  
93:}  

结果输出:
读者写者问题
结果分析:
这里我们可以直接设置buffer为1,也就是文件读写只有一个文件,方便验证效果。注意变红区域,一开始写者尚未写读者直接读出,所以content没有信息。后边写者两个进程都往其中写入了内容,当然是后者覆盖了前者,所以读者两个线程读出了writer0 的内容,这里表面读者可以并行读出。并且,只要还有读者在读,写者就不能写。实验预期目标达成。

5.4. 写者优先的读者-写者问题

94:#define N 1  
95:#define MAXNLOOP 30  
96:  
97:int readercount =0;  
98:int writercount =0;  
99:  
100:// mutex for make sure writer first, wmutex1 lock for writercount, wmutex2 to mutex other writers,  
101:// rmutex lock for readercount  
102:  
103:HANDLE WINAPI mutex = CreateMutex(NULL, FALSE, "MutexToProtectCriticalResource");  
104:HANDLE WINAPI wmutex1 = CreateMutex(NULL, FALSE, "MutexToProtectCriticalResource");  
105:HANDLE WINAPI wmutex2 = CreateMutex(NULL, FALSE, "MutexToProtectCriticalResource");  
106:HANDLE WINAPI rmutex = CreateMutex(NULL, FALSE, "MutexToProtectCriticalResource");  
107:  
108:string buffer[N];  
109:int nloop = 0;  
110:int in = 0;  
111:int out = 0;  
112:  
113:DWORD WINAPI ThreadExecuteZZWWriter(LPVOID lpParameter){  
114:    int *pID = (int*)lpParameter;  
115:    string nextp = to_string(*pID) + " wrote";  
116:  
117:    while(1){  
118:        WaitForSingleObject(wmutex1, INFINITE);  
119:        if(nloop >= MAXNLOOP) break;  
120:        if(writercount==0) WaitForSingleObject(mutex, INFINITE);  
121:        ++writercount;  
122:        ReleaseMutex(wmutex1);  
123:  
124:        WaitForSingleObject(wmutex2, INFINITE);  
125:        buffer[in] = nextp;  
126:        in = (in + 1) % N;  
127:        ++nloop;  
128:        cout << "nloop " << nloop << " writer " <<  *pID << " has wrote on the buffer " << in << "\n";  
129:  
130:        ReleaseMutex(wmutex2);  
131:  
132:        WaitForSingleObject(wmutex1, INFINITE);  
133:        --writercount;  
134:        if(writercount==0) ReleaseMutex(mutex);  
135:        ReleaseMutex(wmutex1);  
136:    }  
137:    return 0;  
138:}  
139:  
140:DWORD WINAPI ThreadExecuteZZWReader(LPVOID lpParameter){  
141:    int *pID = (int*)lpParameter;  
142:    string nextp;  
143:    while(1){  
144:        WaitForSingleObject(mutex, INFINITE);  
145:        WaitForSingleObject(rmutex, INFINITE);  
146:        if(readercount == 0) WaitForSingleObject(wmutex2, INFINITE);  
147:        ++readercount;  
148:        if(nloop>=MAXNLOOP) break;  
149:  
150:        ReleaseMutex(rmutex);  
151:        ReleaseMutex(mutex);  
152:  
153:        nextp = buffer[out];  
154:        out = (out +1) % N;  
155:        ++nloop;  
156:        cout << "nloop " << nloop << " reader "<< *pID << " has read on the buffer " << out << " the content is writer "<< nextp << "\n";  
157:  
158:        WaitForSingleObject(rmutex, INFINITE);  
159:        --readercount;  
160:        if(readercount==0) ReleaseMutex(wmutex2);  
161:        ReleaseMutex(rmutex);  
162:    };  
163:    return 0;  
164:}  

写者优先的读者写者问题
理想的结果是,writercount大于0,即有写者在排队,这时,读者是被阻塞的,但是这样的结果目前没有很好的方法可视化,只能简单从理论上的代码上进行判断,实验成功。

5.5. 哲学家就餐问题

核心代码:

165:#define MAXNLOOP 30  
166:int nloop = 0;  
167:HANDLE WINAPI chopstick0 = CreateMutex(NULL, FALSE, "0");  
168:HANDLE WINAPI chopstick1 = CreateMutex(NULL, FALSE, "1");  
169:HANDLE WINAPI chopstick2 = CreateMutex(NULL, FALSE, "2");  
170:HANDLE WINAPI chopstick3 = CreateMutex(NULL, FALSE, "3");  
171:HANDLE WINAPI chopstick4 = CreateMutex(NULL, FALSE, "4");  
172:HANDLE WINAPI chopstick[5];  
173:HANDLE WINAPI mutex = CreateMutex(NULL, FALSE, "mutex");  
174:DWORD WINAPI ThreadExecuteZZWPhilosopher(LPVOID lpParameter){  
175:    int *pID = (int *)lpParameter;  
176:  
177:    while(1){  
178://        WaitForSingleObject(mutex, INFINITE);  
179:        cout << "Philosopher " << *pID << " is thinking" << endl;  
180://        ReleaseMutex(mutex);  
181:  
182:        WaitForSingleObject(mutex, INFINITE);  
183://        WaitForSingleObject(semCount, INFINITE);  
184:        WaitForSingleObject(chopstick[*pID], INFINITE);  
185:        WaitForSingleObject(chopstick[((*pID)+1)%5], INFINITE);  
186:        WaitForSingleObject(mutex, INFINITE);  
187:  
188:        if(nloop > MAXNLOOP) break;  
189:        cout << "nloop " << nloop << " Philosopher " << *pID << " is eating" << endl;  
190:        ++nloop;  
191://        ReleaseMutex(mutex);  
192:        ReleaseMutex(chopstick[((*pID)+1)%5]);  
193:        ReleaseMutex(chopstick[*pID]);  
194://        ReleaseSemaphore(semCount, 1, NULL);  
195:    }  
196:}  

(缺一张实验结果图 一直上传失败)

通过实验结果截图,可以看出(输出buffer相互混杂,考虑到实验原理,这里没有采取互斥输出)5个线程并行think,之后线程3拿到筷子开始吃饭,后边1号拿到筷子开始吃饭。实验成功。

6. 实验汇总

6.1. 实验要求完成情况

成功完成实验要求。

6.2. 技术难点及解决方案

一开始在生产者-消费者部分,nloop即实验运行的次数总是会比设定的最大次数多两次。这样的结果,本人认为是判断循环停止条件不在同步机管控范围内,这样在判断时还没有超过最大循环次数,但是这时候有其他线程占用,最后运行出来就是超过了最大循环次数的。我们可以把停止条件位置修改一下,这样就可以在同步管控代码区进行处理,保证循环次数的控制。
又回想到实验5中,也是像原来那样在整个循环块进行条件判断,但是却不会超过循环次数,这里考虑是之前的银行转账问题中只有两个线程,而现在有四个线程。
在写者优先的读者写者问题中,发现可视化写者优先的问题十分难实现,最多只能从代码上判断实验的正确与否,确实比较遗憾。并且,本人发现,读写的顺序和子线程在主线程中创建的顺序基本一致,因为创建的过程是顺序的,并且每个子线程执行的代码又是一样的,所以有这样的结果。这样又降低了实验的可靠性。总之在多线程程序中,代码的的正确性还需要更多的方法去验证。
在哲学家问题中,采用了for循环来创建线程,代码是:
For(int i =0; i<5;++i){
hThread [0] = CreateThread(NULL, 0, ThreadExecuteZZWPhilosopher, &i, 0, NULL
}
结果最后的输出结果,只有线程5在跑,后来发现是i的问题。所有线程在运行时,i的值只在一个地址上自增,最后也是把指针传入线程函数,这样最后i全变成了5,所以有这样的结果。
而且最后发现,所有eating的线程最后都会变成一个,换句话说,一开始,还会出现各个线程之间相互抢筷子,但是最后基本就是只有一个线程在拿筷子,放下再拿。这里认为可能是多核处理器自动把部分线程直接阻塞了,或者是会自动保持有一个线程优先级最高,这样就只有它拿的到筷子。这里还需要深入的学习。

6.3. 实验感想和经验总结

通过实验六的典型同步案例,对线程/进程间的同步有了更深的理解,特别是几个关键的函数。但是,通过最后的交互输出结果也发现,可能线程之间配合在多核系统中还有许多更加复杂的问题需要学习。

6.4. 参考链接

  • ZGSOS操作系统实验指导《实验课题6_典型同步问题模拟处理编程设计与实现》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值