R信号量与条件变量.cpp

前言:在多线程编程中,线程同步是确保程序正确执行的关键。C++提供了多种同步机制,其中信号量(Semaphore)和条件变量(Condition Variable)是两种重要的工具。 

目录

一、信号量

资源访问的 “守门员”

(一)信号量的核心操作

(二)代码示例:模拟打印任务

二、条件变量

线程通信的 “传话筒”

(一)条件变量的核心操作

(二)代码示例:生产者 - 消费者问题

三、信号量和条件变量的区别

总结


一、信号量

资源访问的 “守门员”

信号量,你可以把它想象成一个计数器,它的主要职责是控制对共享资源的访问数量。比如,我们有一个打印机房,里面只有 3 台打印机。那我们就要确保同一时间最多只能有 3 个线程去使用打印机,信号量就能完美地完成这个任务。

(一)信号量的核心操作

信号量主要有两个操作:wait(等待)和 post(发布)。

  • wait 操作 :当一个线程想要访问资源时,它会先进行 wait 操作。如果信号量的计数值大于 0,说明还有空闲资源,计数值减 1,线程可以顺利访问资源;但如果计数值为 0,线程就会被阻塞,直到有其他线程释放资源。

  • post 操作 :当线程使用完资源后,会进行 post 操作,将信号量的计数值加 1,这样如果有线程正在等待这个资源,就有可能被唤醒去访问资源。

(二)代码示例:模拟打印任务

#include <iostream>
#include <thread>
#include <semaphore>
#include <vector>
#include <chrono>

// 定义一个信号量,初始值为 3,表示有 3 台打印机
std::counting_semaphore<3> printerSemaphore(3);

void printDocument(const std::string& docName, int printCount) {
    for (int i = 1; i <= printCount; ++i) {
        printerSemaphore.acquire(); // 等待信号量,尝试获取打印机
        std::cout << "正在打印文档 " << docName << " 的第 " << i << " 页..." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 模拟打印时间
        std::cout << "文档 " << docName << " 的第 " << i << " 页打印完成!" << std::endl;
        printerSemaphore.release(); // 释放信号量,打印完成释放打印机
    }
}

int main() {
    // 创建多个线程模拟多个打印任务
    std::thread t1(printDocument, "论文", 3);
    std::thread t2(printDocument, "报告", 2);
    std::thread t3(printDocument, "作业", 4);

    t1.join();
    t2.join();
    t3.join();

    return 0;
}

代码运行结果及解释

程序运行后,输出如下:

正在打印文档 论文 的第 正在打印文档 作业 的第 正在打印文档 1报告 的第 1 页...
 页...
1 页...
文档 报告 的第 文档 作业 的第 1 页打印完成!
文档 论文 的第 1 页打印完成!
1 页打印完成!
正在打印文档 作业 的第 2 页...
正在打印文档 报告 的第 2 页...
正在打印文档 论文 的第 2 页...
文档 作业 的第 文档 报告 的第 2 页打印完成!
2 页打印完成!
正在打印文档 作业 的第 文档 论文 的第 3 页...
2 页打印完成!
正在打印文档 论文 的第 3 页...
文档 作业 的第 3 页打印完成!
正在打印文档 作业 的第 4 页...
文档 论文 的第 3 页打印完成!
文档 作业 的第 4 页打印完成!

从结果可以看出,最多同时有 3 个线程在打印,符合我们 3 台打印机的设定。每个线程在打印完一页后释放信号量,其他等待的线程就能继续打印。

二、条件变量

线程通信的 “传话筒”

条件变量的作用是让线程之间可以相互通信,使线程能够在某个条件满足时等待,或者在条件满足时唤醒其他线程。它的使用通常要和互斥锁(mutex)配合。

(一)条件变量的核心操作

  • wait 操作 :线程调用 wait 方法后会进入等待状态,并且会释放之前锁定的互斥锁。直到被其他线程唤醒,线程才会重新获取互斥锁并继续执行。

  • notify_one 操作 :用于唤醒一个在条件变量上等待的线程。

  • notify_all 操作 :用于唤醒所有在条件变量上等待的线程。

(二)代码示例:生产者 - 消费者问题

#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <chrono>

std::queue<int> productQueue; // 产品队列
std::mutex queueMutex; // 互斥锁
std::condition_variable cv; // 条件变量
const int maxQueueSize = 10; // 队列最大容量
bool productionDone = false; // 生产是否完成

void producer(int producerId) {
    for (int i = 1; i <= 20; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产时间
        std::unique_lock<std::mutex> lock(queueMutex);
        // 如果队列已满,则等待
        cv.wait(lock, [] { return productQueue.size() < maxQueueSize || productionDone; });
        if (productionDone) {
            return; // 如果生产完成且队列已满,直接退出
        }
        productQueue.push(i);
        std::cout << "生产者 " << producerId << " 生产了产品 " << i << ",当前队列中共有 " << productQueue.size() << " 个产品" << std::endl;
        cv.notify_all(); // 通知消费者有新产品
    }
    // 生产完成后,设置标志位并通知消费者
    {
        std::unique_lock<std::mutex> lock(queueMutex);
        productionDone = true;
    }
    cv.notify_all(); // 最后通知消费者生产已经完成
}

void consumer(int consumerId) {
    while (true) {
        std::unique_lock<std::mutex> lock(queueMutex);
        // 如果队列为空且生产未完成,则等待;如果队列为空且生产完成,则退出
        cv.wait(lock, [] { return !productQueue.empty() || productionDone; });
        if (!productQueue.empty()) {
            int product = productQueue.front();
            productQueue.pop();
            std::cout << "消费者 " << consumerId << " 消费了产品 " << product << ",当前队列中共有 " << productQueue.size() << " 个产品" << std::endl;
            cv.notify_all(); // 通知其他线程
        } else if (productionDone) {
            break; // 如果队列为空且生产完成,退出循环
        }
    }
}

int main() {
    std::thread producers[3]; // 3 个生产者线程
    std::thread consumers[5]; // 5 个消费者线程

    // 启动生产者线程
    for (int i = 0; i < 3; ++i) {
        producers[i] = std::thread(producer, i + 1);
    }
    // 启动消费者线程
    for (int i = 0; i < 5; ++i) {
        consumers[i] = std::thread(consumer, i + 1);
    }

    // 等待所有生产者线程结束
    for (auto& t : producers) {
        t.join();
    }
    // 等待所有消费者线程结束
    for (auto& t : consumers) {
        t.join();
    }

    return 0;
}

代码运行结果及解释

程序运行后的输出:

生产者 1 生产了产品 1,当前队列中共有 1 个产品
消费者 1 消费了产品 1,当前队列中共有 0 个产品
生产者 3 生产了产品 1,当前队列中共有 1 个产品
消费者 1 消费了产品 1,当前队列中共有 0 个产品
生产者 2 生产了产品 1,当前队列中共有 1 个产品
消费者 1 消费了产品 1,当前队列中共有 0 个产品
生产者 1 生产了产品 2,当前队列中共有 1 个产品
消费者 1 消费了产品 2,当前队列中共有 0 个产品
生产者 2 生产了产品 2,当前队列中共有 1 个产品
消费者 4 消费了产品 2,当前队列中共有 0 个产品
生产者 3 生产了产品 2,当前队列中共有 1 个产品
消费者 5 消费了产品 2,当前队列中共有 0 个产品
生产者 1 生产了产品 3,当前队列中共有 1 个产品
消费者 3 消费了产品 3,当前队列中共有 0 个产品
生产者 3 生产了产品 3,当前队列中共有 1 个产品
消费者 3 消费了产品 3,当前队列中共有 0 个产品
生产者 2 生产了产品 3,当前队列中共有 1 个产品
消费者 3 消费了产品 3,当前队列中共有 0 个产品
生产者 1 生产了产品 4,当前队列中共有 1 个产品
消费者 3 消费了产品 4,当前队列中共有 0 个产品
生产者 3 生产了产品 4,当前队列中共有 1 个产品
消费者 5 消费了产品 4,当前队列中共有 0 个产品
生产者 2 生产了产品 4,当前队列中共有 1 个产品
消费者 5 消费了产品 4,当前队列中共有 0 个产品
生产者 1 生产了产品 5,当前队列中共有 1 个产品
消费者 4 消费了产品 5,当前队列中共有 0 个产品
生产者 2 生产了产品 5,当前队列中共有 1 个产品
消费者 2 消费了产品 5,当前队列中共有 0 个产品
生产者 3 生产了产品 5,当前队列中共有 1 个产品
消费者 3 消费了产品 5,当前队列中共有 0 个产品
生产者 1 生产了产品 6,当前队列中共有 1 个产品
消费者 3 消费了产品 6,当前队列中共有 0 个产品
生产者 2 生产了产品 6,当前队列中共有 1 个产品
生产者 3 生产了产品 6,当前队列中共有 2 个产品
消费者 3 消费了产品 6,当前队列中共有 1 个产品
消费者 3 消费了产品 6,当前队列中共有 0 个产品
生产者 1 生产了产品 7,当前队列中共有 1 个产品
消费者 5 消费了产品 7,当前队列中共有 0 个产品
生产者 3 生产了产品 7,当前队列中共有 1 个产品
消费者 5 消费了产品 7,当前队列中共有 0 个产品
生产者 2 生产了产品 7,当前队列中共有 1 个产品
消费者 4 消费了产品 7,当前队列中共有 0 个产品
生产者 1 生产了产品 8,当前队列中共有 1 个产品
生产者 3 生产了产品 8,当前队列中共有 2 个产品
消费者 5 消费了产品 8,当前队列中共有 1 个产品
消费者 2 消费了产品 8,当前队列中共有 0 个产品
生产者 2 生产了产品 8,当前队列中共有 1 个产品
消费者 5 消费了产品 8,当前队列中共有 0 个产品
生产者 1 生产了产品 9,当前队列中共有 1 个产品
消费者 4 消费了产品 9,当前队列中共有 0 个产品
生产者 3 生产了产品 9,当前队列中共有 1 个产品
消费者 3 消费了产品 9,当前队列中共有 0 个产品
生产者 2 生产了产品 9,当前队列中共有 1 个产品
消费者 1 消费了产品 9,当前队列中共有 0 个产品
生产者 3 生产了产品 10,当前队列中共有 1 个产品
消费者 1 消费了产品 10,当前队列中共有 0 个产品
生产者 2 生产了产品 10,当前队列中共有 1 个产品
消费者 2 消费了产品 10,当前队列中共有 0 个产品
生产者 1 生产了产品 10,当前队列中共有 1 个产品
消费者 3 消费了产品 10,当前队列中共有 0 个产品
生产者 3 生产了产品 11,当前队列中共有 1 个产品
消费者 3 消费了产品 11,当前队列中共有 0 个产品
生产者 1 生产了产品 11,当前队列中共有 1 个产品
消费者 1 消费了产品 11,当前队列中共有 0 个产品
生产者 2 生产了产品 11,当前队列中共有 1 个产品
消费者 4 消费了产品 11,当前队列中共有 0 个产品
生产者 3 生产了产品 12,当前队列中共有 1 个产品
消费者 1 消费了产品 12,当前队列中共有 0 个产品
生产者 1 生产了产品 12,当前队列中共有 1 个产品
消费者 1 消费了产品 12,当前队列中共有 0 个产品
生产者 2 生产了产品 12,当前队列中共有 1 个产品
消费者 1 消费了产品 12,当前队列中共有 0 个产品
生产者 3 生产了产品 13,当前队列中共有 1 个产品
消费者 2 消费了产品 13,当前队列中共有 0 个产品
生产者 1 生产了产品 13,当前队列中共有 1 个产品
消费者 3 消费了产品 13,当前队列中共有 0 个产品
生产者 2 生产了产品 13,当前队列中共有 1 个产品
消费者 2 消费了产品 13,当前队列中共有 0 个产品
生产者 1 生产了产品 14,当前队列中共有 1 个产品
生产者 3 生产了产品 14,当前队列中共有 2 个产品
生产者 2 生产了产品 14,当前队列中共有 3 个产品
消费者 3 消费了产品 14,当前队列中共有 2 个产品
消费者 3 消费了产品 14,当前队列中共有 1 个产品
消费者 3 消费了产品 14,当前队列中共有 0 个产品
生产者 1 生产了产品 15,当前队列中共有 1 个产品
消费者 4 消费了产品 15,当前队列中共有 0 个产品
生产者 2 生产了产品 15,当前队列中共有 1 个产品
消费者 3 消费了产品 15,当前队列中共有 0 个产品
生产者 3 生产了产品 15,当前队列中共有 1 个产品
消费者 3 消费了产品 15,当前队列中共有 0 个产品
生产者 1 生产了产品 16,当前队列中共有 1 个产品
消费者 3 消费了产品 16,当前队列中共有 0 个产品
生产者 2 生产了产品 16,当前队列中共有 1 个产品
消费者 2 消费了产品 16,当前队列中共有 0 个产品
生产者 3 生产了产品 16,当前队列中共有 1 个产品
消费者 2 消费了产品 16,当前队列中共有 0 个产品
生产者 1 生产了产品 17,当前队列中共有 1 个产品
消费者 1 消费了产品 17,当前队列中共有 0 个产品
生产者 2 生产了产品 17,当前队列中共有 1 个产品
消费者 1 消费了产品 17,当前队列中共有 0 个产品
生产者 3 生产了产品 17,当前队列中共有 1 个产品
消费者 1 消费了产品 17,当前队列中共有 0 个产品
生产者 2 生产了产品 18,当前队列中共有 1 个产品
消费者 1 消费了产品 18,当前队列中共有 0 个产品
生产者 3 生产了产品 18,当前队列中共有 1 个产品
消费者 1 消费了产品 18,当前队列中共有 0 个产品
生产者 1 生产了产品 18,当前队列中共有 1 个产品
消费者 1 消费了产品 18,当前队列中共有 0 个产品
生产者 2 生产了产品 19,当前队列中共有 1 个产品
消费者 2 消费了产品 19,当前队列中共有 0 个产品
生产者 1 生产了产品 19,当前队列中共有 1 个产品
消费者 1 消费了产品 19,当前队列中共有 0 个产品
生产者 3 生产了产品 19,当前队列中共有 1 个产品
消费者 1 消费了产品 19,当前队列中共有 0 个产品
生产者 2 生产了产品 20,当前队列中共有 1 个产品
消费者 1 消费了产品 20,当前队列中共有 0 个产品

在这个示例中,生产者线程生产产品并放入队列,当队列满时生产者会等待;消费者线程从队列中消费产品,当队列空时消费者会等待。当所有产品生产完成后,生产者会通知消费者生产结束,消费者在确认队列空且生产结束后退出。

三、信号量和条件变量的区别

特性信号量条件变量
核心功能控制对有限资源的访问数量线程间通信,等待条件满足
使用场景限制对一组资源的访问(如连接池)生产者-消费者模式,多阶段处理
操作方式acquire/releasewait/notify_one/notify_all
线程阻塞原因资源不足条件不满足
互斥锁配合内置互斥机制(如counting_semaphore)需要手动配合std::mutex使用
适用场景复杂度简单资源控制复杂线程间通信场景

总结

信号量和条件变量都是 C++ 中实现线程同步的重要工具。信号量主要关注对有限资源的访问控制,确保同一时间对资源的访问数量不超过限定值;而条件变量更侧重于线程之间的通信,让线程能够在特定条件下等待或唤醒其他线程。在实际开发中,要根据具体的应用场景选择合适的同步机制。掌握好这两个工具,你就能在多线程编程的世界里更加得心应手,编写出高效、可靠的并发程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值