当前位置: 首页 > article >正文

【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:我认为打印到函数如果太复杂的话还是得进行加锁,因为它不是原子性的 此时这里没有加锁


http://www.kler.cn/a/586685.html

相关文章:

  • docker pull 镜像问题
  • 【Rust交叉编译】在x86_64架构下交叉编译aarch64-linux-musl版的rust-opencv
  • 面向工业与汽车领域的高安全可靠MCU——AS32X601系列芯片解析
  • 斯坦福:通过认知行为改进LLM推理
  • java设计模式面试题3道
  • 高级java每日一道面试题-2025年2月18日-数据库篇-MySQL 如何做到高可用方案?
  • Java开发之数据库应用:记一次医疗系统数据库迁移引发的异常:从MySQL到PostgreSQL的“dual“表陷阱与突围之路
  • 串排序(信息学奥赛一本通-2048)
  • 【虚幻C++笔记】引擎源码下载及编译步骤
  • Let’s Build AI- 实用AI导航网站
  • 正则表达式全解析 + Java常用示例
  • 多线程到底重不重要?
  • 双指针算法专题之——盛最多水的容器
  • 洛谷P4376 [USACO18OPEN] Milking Order G
  • 垃圾收集算法
  • R语言的移动应用开发
  • JavaWeb全链路学习:8、数据库-sql语句
  • 【免费】1949-2020年各省人均GDP数据
  • C++基础 [三] - 面向对象三
  • 基于SpringBoot实现旅游酒店平台功能十六