导读:本期聚焦于小伙伴创作的《如何用C++实现简单的信号量Semaphore?互斥锁与计数器模拟源码解析》,敬请观看详情。信号量是操作系统中常用的同步机制,用于控制多个线程对共享资源的访问。很多开发者在学习多线程编程时,会尝试用C++模拟实现信号量。本文将介绍如何通过互斥锁和条件变量结合计数器的方式,在C++中实现一个简单的信号量Semaphore。文章会先讲解信号量的核心原理,再给出完整的模拟实现源码,同时解释代码中互斥锁和计数器的作用,帮助开发者理解信号量的底层实现逻辑,解决多线程同步中的资源访问控制问题。

信号量是多线程编程中用于控制并发访问共享资源的同步工具,核心逻辑是通过一个计数器记录可用资源数量,配合等待和释放操作实现线程的阻塞与唤醒。在C++标准库中并没有直接提供信号量实现,我们可以通过互斥锁和条件变量模拟计数器的变化,完成简单信号量的实现。

如何用C++实现简单的信号量Semaphore?互斥锁与计数器模拟源码解析

信号量的核心原理

信号量主要包含两个核心操作:

  • wait(P操作):如果计数器大于0,将计数器减1并继续执行;如果计数器等于0,当前线程阻塞等待,直到有其他线程释放资源。
  • post(V操作):将计数器加1,如果有阻塞的线程等待资源,唤醒其中一个线程。

实现信号量需要保证计数器的修改和线程的阻塞唤醒操作是线程安全的,因此需要使用互斥锁保护计数器的访问,用条件变量实现线程的等待和唤醒。

C++模拟信号量实现源码

下面是使用C++11标准库实现的简单信号量Semaphore类,包含构造、wait、post三个核心方法:

#include <mutex>
#include <condition_variable>

class Semaphore {
private:
    int count;                  // 计数器,记录可用资源数量
    std::mutex mtx;             // 互斥锁,保护count的线程安全访问
    std::condition_variable cv; // 条件变量,用于线程的等待和唤醒

public:
    // 构造函数,初始化计数器为传入的初始值
    explicit Semaphore(int init_count = 0) : count(init_count) {}

    // wait操作,P操作
    void wait() {
        std::unique_lock<std::mutex> lock(mtx);
        // 如果计数器为0,阻塞当前线程,等待条件变量唤醒
        cv.wait(lock, [this]() { return count > 0; });
        // 计数器减1,获取资源
        --count;
    }

    // post操作,V操作
    void post() {
        std::unique_lock<std::mutex> lock(mtx);
        // 计数器加1,释放资源
        ++count;
        // 唤醒一个等待的线程
        cv.notify_one();
    }
};

代码逻辑解析

成员变量作用

count是信号量的核心计数器,记录当前可用的资源数量,初始值由构造函数传入,比如初始值为3表示最多允许3个线程同时访问共享资源。

mtx是互斥锁,所有对count的修改和读取操作都需要先获取该锁,避免多个线程同时修改计数器导致数据不一致。

cv是条件变量,当线程执行wait操作发现计数器为0时,通过cv.wait阻塞当前线程,释放互斥锁;当其他线程执行post操作增加计数器后,通过cv.notify_one唤醒一个阻塞的线程。

核心方法说明

wait方法中,先获取互斥锁,然后调用条件变量的wait方法,传入的lambda表达式作为 predicate,当predicate返回true时(即count大于0),线程不会被阻塞,直接执行后续逻辑将count减1;如果predicate返回false,线程会释放互斥锁并进入阻塞状态,直到被post方法唤醒。

post方法中,同样先获取互斥锁,将count加1后,调用notify_one唤醒一个等待的线程,被唤醒的线程会重新获取互斥锁,然后检查predicate,此时count已经大于0,因此会继续执行将count减1的逻辑。

使用示例

下面是使用上述Semaphore控制3个线程同时访问共享资源的示例:

#include <iostream>
#include <thread>
#include <vector>

// 共享资源访问函数
void access_resource(Semaphore& sem, int thread_id) {
    sem.wait(); // 获取信号量,若资源不足则阻塞
    std::cout << "线程" << thread_id << "获取到资源,开始访问" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟资源访问耗时
    std::cout << "线程" << thread_id << "访问结束,释放资源" << std::endl;
    sem.post(); // 释放信号量,唤醒等待的线程
}

int main() {
    Semaphore sem(3); // 初始化信号量,允许3个线程同时访问
    std::vector<std::thread> threads;
    // 创建5个线程尝试访问资源
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(access_resource, std::ref(sem), i);
    }
    // 等待所有线程执行完成
    for (auto& t : threads) {
        t.join();
    }
    return 0;
}

上述示例中,信号量初始值为3,因此前3个线程会同时获取到资源,后续的2个线程会阻塞等待,直到前面的线程释放资源后被唤醒,实现了对共享资源的并发访问控制。

注意事项

  • 信号量的初始值需要根据实际共享资源的数量设置,避免初始值过大导致资源访问冲突,或者过小导致线程长时间阻塞。
  • wait和post操作需要成对出现,如果一个线程获取了信号量却没有释放,会导致其他等待的线程永远无法被唤醒,出现死锁问题。
  • 上述实现是简单的信号量模拟,没有考虑信号量的析构、复制等场景,实际使用时可以根据需求补充相关逻辑,比如禁止复制信号量对象,避免多个对象操作同一个计数器导致逻辑错误。

C++Semaphore互斥锁计数器修改时间:2026-06-10 06:45:17

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。