C++ STL 线程与互斥量:优雅解决哲学家就餐问题
在并发编程的世界里,'哲学家就餐问题'是一个经典且生动的同步模型。它由艾兹格·迪科斯彻于 1965 年提出,用以阐释死锁、资源竞争等核心概念。今天,我们利用现代 C++ 的 STL 线程库(, , <condition_variable>)来探索几种解决方案,看看如何让这五位'哲学家'既能高效思考,又能和谐就餐,避免陷入死锁或饥饿。
问题场景与挑战
想象一下,五位哲学家围坐在一张圆桌旁,他们的生活只有两种状态:思考和就餐。桌上有五份餐食(或五根筷子),每两位哲学家之间放有一根筷子。哲学家必须同时拿到他左边和右边的两根筷子才能开始就餐,就餐完毕后会放下筷子继续思考。
这个模型直接映射到并发编程:哲学家代表线程,筷子代表竞争性资源(如互斥锁)。最直接的实现可能导致严重的死锁:如果所有哲学家同时拿起左边的筷子,那么所有人都会无限等待右边的筷子被释放,程序将永远停滞。
解决方案一:引入顺序,破坏循环等待(资源分级)
一种有效的策略是给所有资源定义一个全局顺序,并要求线程总是按此顺序申请资源。在我们的场景中,可以为每根筷子编号(0-4)。规定除了最后一位哲学家(编号 4),其他所有哲学家都必须先拿编号较小的筷子,再拿编号较大的筷子。对于最后一位哲学家,则强制其先拿编号较大的筷子(即他右边的筷子),再拿编号较小的筷子(左边的筷子)。这样就破坏了'循环等待'这一死锁必要条件。
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>
#include <cstdlib>
const int NUM_PHILOSOPHERS = 5;
std::mutex chopsticks[NUM_PHILOSOPHERS];
void philosopher_ordered(int id) {
int left = id;
int right = (id + 1) % NUM_PHILOSOPHERS;
// 关键:定义获取顺序。除了最后一位,都是先左后右。
int first = (id == NUM_PHILOSOPHERS - 1) ? right : left;
int second = (id == NUM_PHILOSOPHERS - 1) ? left : right;
() {
std::this_thread::(std::chrono::(() % + ));
chopsticks[first].();
chopsticks[second].();
std::cout << << id << << std::endl;
std::this_thread::(std::chrono::(() % + ));
chopsticks[second].();
chopsticks[first].();
}
}


