哲学家就餐问题是多线程中著名的一个问题,经过前面三章的学习,可以使用多线程来模拟下这个问题了。 问题是这样的,有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();
}
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()
{
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. 总结
虽然代码不多一百来行,但是其用到了线程创建,线程管理,互斥量等知识,细细品味能加深对线程的理解。
4. 相关系列
C++标准线程库之入门C++标准线程库之当前线程管理C++标准线程库之共享资源