C++标准线程库之哲学家就餐问题
哲学家就餐问题是多线程中著名的一个问题,经过前面三章的学习,可以使用多线程来模拟下这个问题了。
问题是这样的,有5个哲学家围着一个小圆餐桌坐了下来,但是桌上只有5根筷子(注意是根),每个哲学家只有全抢到左右手边的筷子才能吃东西。抢到2根筷子的哲学家过1秒后把筷子放回原位置,继续游戏。当只抢到1根,另一手的筷子被其他哲学家抢走时,就放下手中的筷子。
为了简化问题,所有哲学家都是先抢左手,在抢右手的,有兴趣可以自己去实现随机版的,差不多。
1. 问题分析
- 首先,每个哲学家都应该是一个线程;
- 其次,筷子是被共享的资源,所有每个筷子都应该被互斥量保护;
- 很明显,1个互斥量是不够保护5个资源的,至少需要5个;
- 因为需要判断能否上锁,互斥量的lock不能满足要求,而try_lock是可以的;
- 吃完等待1秒继续。
2. 代码实现
#include <iostream>
#include <exception>
#include <mutex>
#include <vector>
#include <string>
#include <algorithm>
#include <functional>
#include <thread>
#include <chrono>
std::mutex outputMutex;
class Chopsticks
{
int _i;
std::mutex _mutex;
public:
explicit Chopsticks() : _i(0) {}
void seti(int i) { _i = i; }
friend std::ostream& operator <<(std::ostream& out, const Chopsticks& chopsticks);
// 使用筷子就是上锁
bool use() throw(std::system_error)
{
try {
return _mutex.try_lock();
}
catch(std::system_error& e) {
throw e;
}
}
// 放下筷子就是解锁
void push_down() throw(std::system_error)
{
try {
return _mutex.unlock();
}
catch(std::system_error& e) {
throw e;
}
}
};
std::ostream& operator <<(std::ostream& out, const Chopsticks& chopsticks)
{
out << " 筷子: " << chopsticks._i << " ";
return out;
}
class Philosopher
{
int _i;
// 拿某个筷子
bool _take(Chopsticks& chopsticks)
{
if(chopsticks.use()) {
std::lock_guard<std::mutex> lock(outputMutex);
std::cout << "哲学家: " << _i << "拿到" << chopsticks << std::endl;
fflush(stdout);
return true;
}
else {
std::lock_guard<std::mutex> lock(outputMutex);
std::cout << "抢夺失败, 哲学家: " << _i << chopsticks << std::endl;
fflush(stdout);
return false;
}
}
// 抢夺
void _seizure(Chopsticks& lc, Chopsticks& rc)
{
while(true)
{
{
std::lock_guard<std::mutex> lock(outputMutex);
std::cout << "哲学家: " << _i << " 开始抢筷子..." << std::endl;
fflush(stdout);
}
// 先拿左边,在那右边
if(_take(lc)) {
if(_take(rc)) {
std::lock_guard<std::mutex> lock(outputMutex);
std::cout << "哲学家: " << _i << "吃饭" << std::endl;
std::cout << "哲学家: " << _i << "放下" << rc << std::endl;
fflush(stdout);
rc.push_down();
}
std::lock_guard<std::mutex> lock(outputMutex);
std::cout << "哲学家: " << _i << "放下" << lc << std::endl;
fflush(stdout);
lc.push_down();
}
// 休息1s
std::this_thread::sleep_for(std::chrono::duration<double>(1));
}
}
public:
explicit Philosopher() : _i(0) {}
void seti(int i) { _i = i; }
// 可调用的
void operator() (Chopsticks& lc, Chopsticks& rc)
{
_seizure(lc, rc);
}
};
int main()
{
// 5个哲学家,5个筷子
std::vector<Philosopher> philosophers(5);
std::vector<Chopsticks> chopsticks(5);
std::vector<std::thread> threads;
// 设置序号
for(int i = 0; i < 5; ++ i) {
philosophers.at(i).seti(i + 1);
chopsticks.at(i).seti(i + 1);
}
// 开启线程
for(int i = 0; i < 4; ++ i) {
std::thread thread(philosophers.at(i), std::ref(chopsticks.at(i)), std::ref(chopsticks.at(i + 1)));
threads.push_back(std::move(thread));
}
std::thread thread(philosophers.at(4), std::ref(chopsticks.at(4)), std::ref(chopsticks.at(0)));
threads.push_back(std::move(thread));
std::for_each(threads.begin(), threads.end(), [](std::thread& t) {
t.join();
});
return 0;
}
3. 总结
虽然代码不多一百来行,但是其用到了线程创建,线程管理,互斥量等知识,细细品味能加深对线程的理解。