C++同步信号量★★★★★
信号量:C++ 20 中添加了 C++ 中的信号量为二元信号量与计数信号量,二元信号量实际为计数信号量模板的特化。
binary_semaphore
:二元信号量类似于互斥量,信号量只有 0 1 。
counting_semaphore
:计数信号量。参考头文件#include <semaphore>
,是一种轻量级同步原语,可以控制对共享资源的访问。
- 构造函数:创建一个具有初始计数值的信号量。这个计数值必须在 [0, LeastMaxValue] 范围内,其中 LeastMaxValue 是信号量模板参数的一部分,表示信号量支持的最大值。
- acquire():尝试获取一个资源单位。如果当前计数大于0,则减少计数并继续执行;如果计数为0,则阻塞直到有可用资源。
- release():释放一个资源单位,增加信号量计数,允许其他等待的线程继续执行。
- try_acquire():尝试获取资源而不阻塞。如果当前计数大于0,则减少计数并返回成功;否则立即返回失败。
- try_acquire_for(std::chrono::duration):尝试在指定的时间段内获取资源。如果在超时前获得资源则返回成功,否则超时后返回失败。
- try_acquire_until(std::chrono::time_point):尝试在给定的时间点之前获取资源。如果在指定时间点前获得资源则返回成功,否则在时间点到达后返回失败。
#include <iostream>
#include <thread>
#include <vector>
#include <semaphore>
std::counting_semaphore<3> sem(3); // 允许最多3个并发访问
void access_resource(int id) {
sem.acquire(); // 尝试获取资源
std::cout << "Thread " << id << " is accessing the resource.\n";
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟资源访问
std::cout << "Thread " << id << " has finished accessing the resource.\n";
sem.release(); // 释放资源
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(access_resource, i);
}
for (auto& t : threads) {
t.join();
}
return 0;
}
C++ 20 以后支持 latch 与 barrier,他们同样可以用来线程同步。
latch
:
- latch(n);:初始化一个 latch,其内部计数器设置为n(必须是正整数)。
- count_down():将latch 的计数器递减 1,如果计数器已经是 0,则此操作无效。这是一个非阻塞操作。
- wait():如果计数器不为 0,则调用该方法的线程将被阻塞,直到计数器变为 0。如果计数器已经是 0,则线程不会被阻塞。
- try_wait():检查计数器是否为 0,如果是,则返回 true;否则返回 false。这允许线程尝试获取锁存状态而不被阻塞。
- arrive_and_wait():等同于依次调用 count_down() 和 wait()。首先将计数器递减 1,然后如果计数器不为 0,则当前线程会被阻塞,直到计数器变为 0。
barrier
:
- std::barrier arriving_threads_count (completion_function) :当所有预期的线程都到达屏障时会被调用。
- arrive_and_wait():标记当前线程已经到达屏障并等待直到所有预期的线程都到达屏障。这个方法相当于递减屏障的计数器并阻塞当前线程直到计数器达到零。
- arrive_and_drop():标记当前线程已经到达屏障并且从参与后续阶段的线程集合中移除自己。这意味着该线程不会参与到下一个屏障阶段中。
- arrive() 和 arrive_and_wait(arrived) / arrive_and_drop(arrived):这些方法允许你获取当前到达屏障的线程数量,但通常直接使用
arrive_and_wait()
或arrive_and_drop()
更为常见。- phase(): 返回当前屏障所处的阶段编号。每个屏障的生命周期包含多个阶段,每当所有预期的线程都到达屏障并执行完完成函数后,屏障进入下一个阶段。
#include <iostream>
#include <thread>
#include <vector>
#include <latch>
#include <barrier>
// 使用 std::latch 进行同步
void latchExample() {
std::latch work_done(3);
auto worker = [&](int id) {
std::this_thread::sleep_for(std::chrono::milliseconds(id * 100)); // 模拟工作
std::cout << "Worker " << id << " has finished.\n";
work_done.count_down();
};
std::vector<std::thread> workers;
for (int i = 1; i <= 3; ++i) {
workers.emplace_back(worker, i);
}
work_done.wait(); // 等待所有工作完成
std::cout << "All workers have finished their tasks using latch.\n";
for (auto& t : workers) {
if (t.joinable()) t.join();
}
}
// 使用 std::barrier 实现类似的单次同步
void barrierExample() {
const auto workers = {
1, 2, 3};
std::barrier sync_point(3, []{
std::cout << "All workers have finished their tasks using barrier.\n"; });
auto work = [&](int id) {
std::this_thread::sleep_for(std::chrono::milliseconds(id * 100)); // 模拟工作
std::cout << "Worker " << id << " has finished.\n";
sync_point.arrive_and_wait();
};
std::vector<std::thread> threads;
for (auto const& worker : workers) {
threads.emplace_back(work, worker);
}
for (auto& thread : threads) {
if (thread.joinable()) thread.join();
}
}
int main() {
std::cout << "Using latch:\n";
latchExample();
std::cout << "\nUsing barrier for single synchronization point:\n";
barrierExample();
return 0;
}
C++互斥信号量★★★★★
互斥量(mutex): 防止多个线程对临界区同时操作而产生不一致的问题。mutex只有锁定(locked)和未锁定(unlocked)两种状态。任何时候,至多只有一个线程可以锁定互斥量。试图对已经锁定的互斥量再次加锁,将可能阻塞线程或者报错失败,mutex的底层可能封装的是操作系统 spinlock,不同的操作系统下可能有不同的实现。C++ 中关于 mutex 的头文件为
#include <mutex>
。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void print_block (int n, char c) {
mtx.lock();
for (int i=0; i<n; ++i) {
std::cout << c