【c++】【线程】【信号量】三个线程顺序打印1--100
【c++】【线程】【信号量】三个线程顺序打印1–100
eg:a:1–>b:2–>c:3–>a:4–>b:5…
思路:
- 使用三个信号量s1 s2 s3
- s1=1,s2=0,s3=0
- 线程a p(s1)–>打印数据–>v(s2)
- 线程b p(s2)–>打印数据–>v(s3)
- 线程a p(s3)–>打印数据–>v(s1)
代码:c++20 之前没有信号量 这个Semaphore类通过条件变量和互斥锁实现
c++20 后直接用就行
#include <iostream>
#include <thread>
#include <semaphore>
#include <mutex>
#include <condition_variable>
using namespace std;
class Semaphore {
public:
// 构造函数:初始计数 count
explicit Semaphore(int count = 0) : count_(count) {}
// P 操作:等待并减少计数
void acquire() {
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [&] { return count_ > 0; });
--count_;
}
// V 操作:释放并增加计数,通知等待线程
void release() {
std::unique_lock<std::mutex> lock(mutex_);
++count_;
cv_.notify_one();
}
private:
std::mutex mutex_;
std::condition_variable cv_;
int count_;
};
#include <iostream>
#include <thread>
using namespace std;
Semaphore s1(1); // 允许 a1 先执行
Semaphore s2(0);
Semaphore s3(0);
int counter = 1;
const int maxCount = 100;
void a1() {
while (true) {
s1.acquire(); // 等待信号量 s1
if (counter > maxCount) {
s2.release(); // 唤醒 a2,便于退出循环
break;
}
// 打印操作(将输出一次性组装,保证原子性)
cout <<"a1:" << counter++ << " ";
s2.release(); // 释放信号量,唤醒 a2
}
}
void a2() {
while (true) {
s2.acquire();
if (counter > maxCount) {
s3.release();
break;
}
cout << "a2:" << counter++ << " ";
s3.release();
}
}
void a3() {
while (true) {
s3.acquire();
if (counter > maxCount) {
s1.release();
break;
}
cout << "a3:" << counter++ <<" ";
s1.release();
}
}
int main() {
thread t1(a1);
thread t2(a2);
thread t3(a3);
t1.join();
t2.join();
t3.join();
cout << endl;
return 0;
}
执行结果:
- 自定义 Semaphore 类
使用 std::mutex 和 std::condition_variable 实现信号量的基本功能。
acquire():等待直到内部计数器大于 0,然后将计数器减 1。
release():增加计数器并通知等待线程。 - 线程函数 a1, a2, a3
每个线程首先调用 acquire() 来等待信号量。
检查计数器是否超过最大值 maxCount,如果超过则释放下一个信号量,便于其他线程退出循环。
如果未超过,则打印当前数字并自增,然后调用 release() 唤醒下一个线程。 - 主函数
创建三个线程并等待它们结束,最后打印换行符结束输出。
std::unique_lock<std::mutex> lock(mutex_)
是 C++ 标准库中用于管理互斥锁(mutex)的一个对象创建语句,它在作用域内自动管理锁的获取与释放。
1. 语法解析
std::unique_lock<std::mutex> lock(mutex_);
std::unique_lock<std::mutex>
这是一个模板类 unique_lock,其模板参数为互斥锁的类型(这里是 std::mutex)。
2. 工作原理
2.1 自动获取互斥锁
-
当执行到这行代码时,
unique_lock
的构造函数会自动调用mutex_.lock()
,从而获得对mutex_
的所有权。 -
这样可以确保在后续的代码中,只有获得该锁的线程能够进入临界区,从而防止多个线程同时访问共享资源导致数据竞争。
-
当 unique_lock 对象离开作用域时,自动释放锁
-
通过
std::unique_lock<std::mutex> lock(mutex_);
,你可以确保进入临界区的代码在离开作用域时自动释放锁,从而避免死锁和资源泄漏问题。
cv_.notify_one();
是用于唤醒等待该条件变量的一个
线程。具体解释如下:
1. 条件变量的作用
- 条件变量(std::condition_variable)允许线程在等待某个条件满足时进入休眠状态,以便在条件满足时被其他线程唤醒继续执行。
- 线程通过调用 wait() 方法进入等待状态,等待某个特定的条件为真。
2. notify_one() 的作用
- 唤醒等待线程:notify_one() 会通知(唤醒)至少一个正在等待这个条件变量的线程。
- 如果有多个线程在等待,系统会选择其中一个线程唤醒(具体选择哪个取决于实现)。
- 被唤醒的线程会重新获取关联的互斥锁(
mutex
),然后检查等待条件是否满足,进而继续执行后续代码。
3. 使用场景举例
假设有一个线程 A 在等待某个条件:
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, []{ return condition; });
当线程 B 修改了某个数据,并使 condition
成为 true
时,线程 B 调用:
cv_.notify_one();
这会唤醒线程 A,使其结束等待,重新获取锁后检查条件,然后继续执行后续代码。
4. 与 notify_all()的区别
- notify_one():唤醒一个等待的线程。
- notify_all():唤醒所有等待该条件变量的线程,通常在条件变化后所有线程都需要重新检查条件时使用。
总结
- cv_.notify_one(); 是条件变量的一个通知函数,用于唤醒一个在等待该条件变量的线程,使其能够重新获得互斥锁并检查等待条件。
- 这种机制有助于线程间同步,防止资源竞争和忙等待。
ps:我认为打印到函数如果太复杂的话还是得进行加锁,因为它不是原子性的 此时这里没有加锁