一、原理
面包店算法(Bakery Algorithm)是一个用于实现多进程或多线程之间同步的算法,由荷兰计算机科学家Edsger W. Dijkstra于1974年提出。这个算法的名称来源于现实生活中面包店的取号系统。
面包店算法的具体实现如下:
- 每个线程(或进程)想要进入临界区执行时,必须先拿一个号码。拿号码的规则是查看所有线程的号码,拿一个比所有人号码都大的号码,或者在没有线程在临界区的情况下,可以拿号码0。这个过程类似于面包店里的取号系统,客人进门取一个比当前所有等待的客人号码都大的号。
- 线程进入临界区的规则是,号码小的线程优先进入。如果两个线程号码相同,则线程ID小的优先进入。这就像面包店服务员为号码小的客人服务。
- 线程完成临界区的任务后,会将自己的号码设置为0,表示已经退出临界区。
面包店算法中的关键在于解决线程在拿号码时的冲突,以及持有相同号码时的优先级问题,这样可以保证每个线程都有机会进入临界区,从而避免了饥饿现象,实现了公平的资源访问。
二、C++代码实现
#include <iostream>
#include <thread>
#include <atomic>
#include <vector>
#include <array>
#include <algorithm>
constexpr int loop_count = 5;
constexpr int thread_count = 10;
// 0或1,代表正在取票与否
std::array<int, thread_count> choosing;
// 票号
std::array<int, thread_count> tickets;
void critical_region(int thread_id) {
std::cout << "thread " << thread_id << " is in critical_region" << '\n';
}
void non_critical_region(int thread_id) {
std::cout << "thread " << thread_id << " is in non_critical_region" << '\n';
}
void lamport_bakery_algorithm(int thread_id) {
for (int loop = 0; loop < loop_count; loop++) {
// 取票
choosing[thread_id] = 1;
tickets[thread_id] = 1 + *std::max_element(&tickets[0], &tickets[thread_count - 1]);
choosing[thread_id] = 0;
// 拿面包
for (int i = 0; i < thread_count; i++) {
// 有线程正在取票,让出cpu
while (choosing[i] == 1) {
std::this_thread::yield();
}
// 有线程的票号较小,或票号相等但线程号较小,让出cpu
while (tickets[i] != 0 && (tickets[i] < tickets[thread_id] || (tickets[i] == tickets[thread_id] && i < thread_id))) {
std::this_thread::yield();
}
}
// 执行临界区代码
critical_region(thread_id);
// 票号重置
tickets[thread_id] = 0;
// non_critical_region(thread_id);
}
}
int main() {
std::vector<std::thread> thread_pool;
for (int i = 0; i < thread_count; i++) {
choosing[i] = 0;
tickets[i] = 0;
thread_pool.push_back(std::thread(lamport_bakery_algorithm, i));
}
for (auto& thread : thread_pool) {
thread.join();
}
return 0;
}