下面通过“哲学家就餐问题”说明了如何使用 concurrency::join 类来避免在应用程序中发生死锁。 在软件应用中,如果两个或多个进程分别留有资源,且相互等待另一进程释放其他资源,就会发生死锁。
“哲学家就餐问题”是在多个并发进程之间共享一组资源时可能发生的一组问题的一个特定示例。
哲学家就餐问题
“哲学家就餐问题”说明了应用程序中的死锁是如何发生的。 在这个问题中,五位哲学家围坐在一张圆形餐桌旁。 每位哲学家都在思考和饮食两种行为之间交替。 每位哲学家必须与左边的邻居共用一根筷子,与右边的邻居共用另一根筷子。 下图显示了这一布局。
要想吃东西,一位哲学家必须拿起两根筷子。 如果每位哲学家只拿着一根筷子,并等待另一根筷子,那么哪位哲学家都无法吃东西,全部都会饿死。
Naïve 实现
下面的示例演示了“哲学家就餐问题”的 Naïve 实现。 派生自 concurrency::agent 的 philosopher 类使每位哲学家可以独立行动。 该示例使用 concurrency::critical_section 对象的共享数组为每个 philosopher 对象授予对一双筷子的独占访问权。
为了将该实现与图示相关联,philosopher 类表示一位哲学家。 一个 int 变量表示每根筷子。 critical_section 对象充当放置筷子的托架。 run 方法模拟哲学家的生命。 think 方法模拟思考行为,eat 方法模拟进食行为。
philosopher 对象会锁定两个 critical_section 对象,以模拟在调用 eat 方法之前从托架上拿走筷子的行为。 调用 eat 后,philosopher 对象通过将 critical_section 对象设置为解锁状态,将筷子放回托架。
pickup_chopsticks 方法说明死锁的发生位置。 如果每个 philosopher 对象都获得了对其中一个锁的访问权限,则无法继续任何 philosopher 对象,因为另一个锁由另一个 philosopher 对象控制。
// philosophers-deadlock.cpp
// compile with: /EHsc
#include <agents.h>
#include <string>
#include <array>
#include <iostream>
#include <algorithm>
#include <random>
using namespace concurrency;
using namespace std;
// Defines a single chopstick.
typedef int chopstick;
// The total number of philosophers.
const int philosopher_count = 5;
// The number of times each philosopher should eat.
const int eat_count = 50;
// A shared array of critical sections. Each critical section
// guards access to a single chopstick.
critical_section locks[philosopher_count];
// Implements the logic for a single dining philosopher.
class philosopher : public agent
{
public:
explicit philosopher(chopstick& left, chopstick& right, const wstring& name)
: _left(left)
, _right(right)
, _name(name)
, _random_generator(42)
{
send(_times_eaten, 0);
}
// Retrieves the number of times the philosopher has eaten.
int times_eaten()
{
return receive(_times_eaten);
}
// Retrieves the name of the philosopher.
wstring name() const
{
return _name;
}
protected:
// Performs the main logic of the dining philosopher algorithm.
void run()
{
// Repeat the thinks/eat cycle a set number of times.
for (int n = 0; n < eat_count; ++n)
{
think();
pickup_chopsticks();
eat();
send(_times_eaten, n+1);
putdown_chopsticks();
}
done();
}
// Gains access to the chopsticks.
void pickup_chopsticks()
{
// Deadlock occurs here if each philosopher gains access to one
// of the chopsticks and mutually waits for another to release
// the other chopstick.
locks[_left].lock();
locks[_right].lock();
}
// Releases the chopsticks for others.
void putdown_chopsticks()
{
locks[_right].unlock();
locks[_left].unlock();
}
// Simulates thinking for a brief period of time.
void think()
{
random_wait(100);
}
// Simulates eating for a brief period of time.
void eat()
{
random_wait(100);
}
private:
// Yields the current context for a random period of time.
void random_wait(unsigned int max)
{
concurrency::wait(_random_generator()%max);
}
private:
// Index of the left chopstick in the chopstick array.
chopstick& _left;
// Index of the right chopstick in the chopstick array.
chopstick& _right;
// The name of the philosopher.
wstring _name;
// Stores the number of times the philosopher has eaten.
overwrite_buffer<int> _times_eaten;
// A random number generator.
mt19937 _random_generator;
};
int wmain()
{
// Create an array of index values for the chopsticks.
array<chopstick, philosopher_count> chopsticks = {0, 1, 2, 3, 4};
// Create an array of philosophers. Each pair of neighboring
// philosophers shares one of the chopsticks.
array<philosopher, philosopher_count> philosophers = {
philosopher(chopsticks[0], chopsticks[1], L"aristotle"),
philosopher(chopsticks[1], chopsticks[2], L"descartes"),
philosopher(chopsticks[2], chopsticks[3], L"hobbes"),
philosopher(chopsticks[3], chopsticks[4], L"socrates"),
philosopher(chopsticks[4], chopsticks[0], L"plato"),
};
// Begin the simulation.
for_each (begin(philosophers), end(philosophers), [](philosopher& p) {
p.start();
});
// Wait for each philosopher to finish and print his name and the number
// of times he has eaten.
for_each (begin(philosophers), end(philosophers), [](philosopher& p) {
agent::wait(&p);
wcout << p.name() << L" ate " << p.times_eaten() << L" times." << endl;
});
}
编译代码
复制示例代码,并将它粘贴到 Visual Studio 项目中,或粘贴到名为 philosophers-deadlock.cpp 的文件中,再在 Visual Studio 命令提示符窗口中运行以下命令。
cl.exe /EHsc philosophers-deadlock.cpp