多线程编程std::thread、std::mutex和信号量

在多线程编程中,std::threadstd::mutex和信号量(例如在这个上下文中提到的Semaphore::Ptr)是三种常用的并发控制机制,它们各自扮演着不同的角色。下面是它们之间关系的解释:

std::thread

  • std::thread是C++11引入的一个库类,用于管理线程。它允许程序员创建和控制线程的执行,每个std::thread对象都代表一个单独的执行线程。通过std::thread,你可以指定线程开始时执行的函数或者可调用对象。

std::mutex

  • std::mutex(互斥量)是一种基本的同步原语,用于控制对共享资源的访问。在多线程环境中,当多个线程需要访问同一资源(如共享内存区)时,std::mutex可以保证在任意时刻只有一个线程可以访问该资源。通过锁定(lock)和解锁(unlock)互斥量,它帮助避免了数据竞争和条件竞争等问题。

Semaphore::Ptr

  • 信号量(Semaphore)是另一种同步机制,用于控制对资源的访问数量。不同于互斥量在任意时刻只允许一个线程访问资源,信号量允许多个线程根据信号量当前的计数值访问共享资源。信号量的计数值表示了可用资源的数量。线程通过增加(signal或release)或减少(wait或acquire)信号量的计数来获取或释放资源。
  • 在给出的上下文中,Semaphore::Ptr可能是一个信号量对象的智能指针,表示该对象是通过某种智能指针(如std::shared_ptr)管理的,这有助于自动管理信号量对象的生命周期。

它们之间的关系

  • 协作关系:在多线程程序中,std::thread用于创建和管理线程,而std::mutexSemaphore::Ptr则用于线程之间的同步和互斥,确保线程安全地访问共享资源。
  • 使用场景std::mutex通常用于保护数据结构或代码段,防止同时由多个线程修改。信号量(如Semaphore::Ptr所示)则用于限制对资源的并发访问数量,它可以是任意正数,非常适合管理有限数量的资源,如连接池、任务队列等。

目标

创建两个线程:一个生产者(producer)和一个消费者(consumer)。生产者生成数据并存储到共享缓冲区,消费者从共享缓冲区读取数据。我们将使用互斥量保护共享数据以避免竞态条件,并使用信号量来同步生产者和消费者的操作。

实现自定义信号量

首先,我们需要一个信号量类。C++标准库没有直接提供信号量,但我们可以利用std::mutexstd::condition_variable来模拟它:

class Semaphore {
public:
// 构造函数初始化信号量的计数器count_。参数count默认为0,意味着信号量在创建时没有资源可用。这个计数器表示信号量当前可用的资源数。
    Semaphore(int count = 0) : count_(count) {}

//函数用于增加信号量的计数器,表示释放或添加一个资源
    void notify() {
        //函数首先通过std::mutex创建一个std::unique_lock对象lock,确保对count_修改的操作是线程安全的。std::unique_lock在构造时自动锁定互斥量,并在析构时自动解锁,提供了一种方便的RAII(资源获取即初始化)风格的同步方法。
        std::unique_lock<std::mutex> lock(mutex_);
        //++count_操作将信号量的计数值增加1。
        ++count_;
        //调用std::condition_variable的notify_one方法,唤醒一个等待(wait())在该条件变量上的线程。如果有多个线程在等待,只有一个线程会被随机选择并唤醒。
        cv_.notify_one(); 
    }
    
//用于减少信号量的计数器,表示获取一个资源。如果没有可用资源(即count_为0),调用此函数的线程将阻塞,直到count_变为非零。
    void wait() {
        std::unique_lock<std::mutex> lock(mutex_);
        while (count_ == 0) {
            //cv_.wait(lock)将会原子性地解锁互斥量并阻塞当前线程
            cv_.wait(lock);
        }
        //操作将信号量的计数值减少1,表示一个资源已经被成功获取。
        --count_;
    }

private:
    std::mutex mutex_;
    std::condition_variable cv_;
    int count_;
};

示例代码

实现生产者-消费者模型:

#include <mutex>
#include <condition_variable>
#include <iostream>
#include <vector>
#include <thread>


int shared_data = 0;
std::mutex mtx;

//这个Semaphore类提供了一个基本的信号量实现,利用互斥量(std::mutex)和条件变量(std::condition_variable)实现线程之间的同步。通过notify()和wait()函数的调用,多个线程可以安全地共享资源,
//而不会发生数据竞争。这种信号量实现特别适用于需要控制对数量有限的资源访问的场景。


class Semaphore {
public:
// 构造函数初始化信号量的计数器count_。参数count默认为0,意味着信号量在创建时没有资源可用。这个计数器表示信号量当前可用的资源数。
    Semaphore(int count = 0) : count_(count) {}

//函数用于增加信号量的计数器,表示释放或添加一个资源
    void notify() {
        //函数首先通过std::mutex创建一个std::unique_lock对象lock,确保对count_修改的操作是线程安全的。std::unique_lock在构造时自动锁定互斥量,并在析构时自动解锁,提供了一种方便的RAII(资源获取即初始化)风格的同步方法。
        std::unique_lock<std::mutex> lock(mutex_);
        //++count_操作将信号量的计数值增加1。
        ++count_;
        //调用std::condition_variable的notify_one方法,唤醒一个等待(wait())在该条件变量上的线程。如果有多个线程在等待,只有一个线程会被随机选择并唤醒。
        cv_.notify_one(); 
    }
    
//用于减少信号量的计数器,表示获取一个资源。如果没有可用资源(即count_为0),调用此函数的线程将阻塞,直到count_变为非零。
    void wait() {
        std::unique_lock<std::mutex> lock(mutex_);
        while (count_ == 0) {
            //cv_.wait(lock)将会原子性地解锁互斥量并阻塞当前线程
            cv_.wait(lock);
        }
        //操作将信号量的计数值减少1,表示一个资源已经被成功获取。
        --count_;
    }

private:
    std::mutex mutex_;
    std::condition_variable cv_;
    int count_;
};

// empty信号量用于控制生产者可以生产的数据数量,避免过度生产导致资源浪费或溢出。full信号量则用于指示消费者可消费的数据数量,确保消费者不会在没有数据可用时尝试消费。
// Semaphore& empty:一个引用传递的Semaphore对象,代表空闲资源的数量。在生产者视角中,它表示可以用于生产的空间数量。
// Semaphore& full:另一个引用传递的Semaphore对象,代表已填充资源的数量。在生产者视角中,它表示已经生产但尚未被消费的产品数量。
void producer(Semaphore& empty, Semaphore& full) {
    for (int i = 0; i < 10; ++i) {
        empty.wait(); //等待空闲资源
        std::unique_lock<std::mutex> lock(mtx);
        //生产数据
        shared_data = i;
        //显示生产者在生产什么
        std::cout << "produced:" << shared_data << std::endl;
        // 生产完毕后,生产者通过调用full.notify()来增加full信号量的计数,表示新增了一个可供消费的数据项。这一操作实际上是通知等待在full信号量上的消费者线程有新数据可用。
        full.notify();

    }
}

void consumer(Semaphore& empty, Semaphore& full) {
    for (int i = 0; i < 10; i++) {
        full.wait(); //等待数据可用
        // lock: 是一个std::unique_lock<std::mutex>类型的对象实例,通过构造时传入的mtx互斥锁,自动对mtx进行锁定。lock对象的生命周期管理着mtx的锁定状态,当lock对象被销毁(即离开作用域时),它会自动解锁mtx。
        std::unique_lock<std::mutex> lock(mtx);
        //消费数据
        std::cout << "Consumed:" << shared_data << std::endl;
        empty.notify(); //通知生产者有空闲资源


    }
}

int main() {
    Semaphore empty(1); //初始时有一个空闲资源
    Semaphore full(0); //初始时没有数据可用

    std::thread producerThread(producer, std::ref(empty),std::ref(full));
    std::thread consumereThread(consumer, std::ref(empty),std::ref(full));

    producerThread.join();
    consumereThread.join();
}

运行结果 

produced:0
Consumed:0
produced:1
Consumed:1
produced:2
Consumed:2
produced:3
Consumed:3
produced:4
Consumed:4
produced:5
Consumed:5
produced:6
Consumed:6
produced:7
Consumed:7
produced:8
Consumed:8
produced:9
Consumed:9

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值