从生产者-消费者模型中学习互斥量,锁,条件变量

这篇具有很好参考价值的文章主要介绍了从生产者-消费者模型中学习互斥量,锁,条件变量。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

经典的并发控制模型

主要是练习mutex unique_lock conditional_variable
[[20 原子操作]]

一、互斥量

1 mutex 互斥量

mutex 是一种互斥的同步原语,用于保护共享资源的访问,确保在同一时间只有一个线程可以访问共享资源。通过对互斥量加锁和解锁,可以实现对共享资源的独占访问。

2 shared_mutex 共享互斥量

允许多个线程同时获取共享访问权限,适用于读多写少的场景。 要想实现共享的概念,可以使用与shared_lock 包装器搭配使用[[10 生产者-消费者模型(互斥量,锁,条件 变量)#4 shared_lock]]。

二、互斥量包装器

1 lock_guard

轻量级的互斥量包装器,用于自动获取互斥锁并在作用域结束时自动释放锁。它适用于临界区的简单互斥保护,没有额外的灵活性

2 unique_lock 独占锁

unique_lock 是一个灵活的互斥量包装器,提供了更高级别的互斥操作。它是基于互斥量的封装,可以通过构造函数或成员函数的方式对互斥量进行加锁和解锁。与普通的 lock()unlock() 不同,unique_lock 在创建时可以选择性地对互斥量进行加锁,也可以在任何时候手动加锁或解锁。
mtx.lock() 和 mtx.unlock();

  • 自动加锁和解锁
  • 延迟加解锁,允许在需要时手动加锁和解锁,如加锁、解锁、尝试加锁等:lock.lock();, lock.unlock();, lock.try_lock();
  • 可移动性, std::unique_lock 对象可以被移动,因此可以在不同线程之间传递所有权。这对于实现资源所有权的转移非常有用。
3 scoped_lock

C++17引入的互斥量包装器,可以同时锁定任意数量的互斥量,还可以避免死锁,因为它使用了无死锁的算法来获取多个互斥量。它会按照参数的顺序来进行加锁,并确保在作用域结束时以相反的顺序释放锁。
不支持手动的加锁和解锁操作

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx1;
std::mutex mtx2;

void Worker()
{
    // 同时锁定mtx1和mtx2
    std::scoped_lock lock(mtx1, mtx2);

    // 执行任务
    std::cout << "Worker: Doing some work..." << std::endl;
}

int main()
{
    std::thread workerThread(Worker);

    // 等待工作线程完成
    workerThread.join();

    return 0;
}

4 shared_lock

共享锁的互斥量包装器,用于实现共享所有权的线程同步。它允许多个线程同时共享对互斥资源的访问,提高了并发性能。

#include <iostream>
#include <thread>
#include <shared_mutex>

std::shared_mutex mtx;
int sharedData = 0;

void Reader()
{
    std::shared_lock lock(mtx);  // 获取共享锁

    // 读取共享数据
    std::cout << "Reader: Shared data = " << sharedData << std::endl;
}

void Writer()
{
    std::unique_lock lock(mtx);  // 获取独占锁

    // 修改共享数据
    sharedData += 1;
    std::cout << "Writer: Incremented shared data" << std::endl;
}

int main()
{
    std::thread readerThread1(Reader);
    std::thread readerThread2(Reader);
    std::thread writerThread(Writer);

    readerThread1.join();
    readerThread2.join();
    writerThread.join();

    return 0;
}

三、条件变量

条件变量(Condition Variable)是一种线程同步的机制,用于线程间的等待和通知。条件变量允许一个或多个线程在满足特定条件之前进行等待,一旦条件满足,其他线程可以通知等待的线程继续执行。

1、使用步骤

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

std::mutex mtx;
std::condition_variable cv;
std::string flag("A");

void PrintA()
{
    while(true) {
        std::unique_lock<std::mutex> lck(mtx);
        while (flag == "B") {
            cv.wait(lck);
        }
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << "A" << std::endl;
        flag = "B";
        cv.notify_all();
    }
}

void PrintB()
{
    while(true) {
        std::unique_lock<std::mutex> lck(mtx);
        while (flag == "A") {
            cv.wait(lck);     // cv.wait(lck, lambda)
        }
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << "B" << std::endl;
        flag = "A";
        cv.notify_all();
    }
}

int main()
{
    std::thread t1(PrintA);
    std::thread t2(PrintB);
    t1.join();
    t2.join();
    std::cin.get();
}

2, 调用 wait() 后等待过程
用于在等待条件满足时将线程置于等待状态,并释放所持有的互斥锁。

  1. 线程首先获取与条件变量关联的互斥锁(std::mutex)的所有权,这是通过调用 std::unique_lock 构造函数来实现的。

  2. 然后,线程进入等待状态,并且该线程的执行被暂时阻塞,等待条件满足或被唤醒。

  3. 在等待状态下,线程会自动释放互斥锁,允许其他线程获得锁并对共享数据进行操作。

  4. 当条件变量的 notify_one()notify_all() 函数被调用并且相应的通知被发送时,线程会从等待状态中被唤醒。

  5. 被唤醒的线程尝试重新获得互斥锁的所有权。一旦成功获得锁,线程将退出 wait() 函数并继续执行后续的代码。

注意: 在进行条件判断的时候使用的是while而不是if,这是为了防止虚假唤醒(Spurious Wakeup) ,当线程被唤醒时,它会再次检查条件,如果条件不满足,则继续等待。这样可以确保只有在条件真正满足时才会跳出循环继续执行后续的代码。通常建议始终使用 while 循环来判断条件。

在wait中使用lambda时,只有当返回 false 时,线程会进入等待状态;当谓词返回 true 时,线程会继续执行后续的代码。这与使用while是相反的。

3,什么时候会发生虚假唤醒

虚假唤醒的发生是由操作系统和硬件等因素引起的,具体原因可能包括但不限于以下情况:

  1. 优化和调度:操作系统和硬件可能会进行一些优化和调度策略,导致等待线程在没有明确通知的情况下被唤醒。这种唤醒是不可预测的,可能是为了提高性能或满足其他系统需求。

  2. 中断和信号:在某些情况下,操作系统可能会向线程发送中断或信号,以响应某些事件或条件的发生。这可能会导致线程被唤醒,即使没有显式的通知也会发生虚假唤醒。

虚假唤醒的发生是无法完全避免的,因此在使用条件变量进行线程同步时,需要进行适当的防护措施。常见的防护措施包括:文章来源地址https://www.toymoban.com/news/detail-459036.html

  • 使用 while 循环进行条件判断
  • 结合条件变量和互斥量
  • 使用谓词进行条件判断:在等待过程中,使用带有谓词的 cv.wait() 函数进行条件判断。

到了这里,关于从生产者-消费者模型中学习互斥量,锁,条件变量的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 线程同步--生产者消费者模型

    条件变量是 线程间共享的全局变量 ,线程间可以通过条件变量进行同步控制 条件变量的使用必须依赖于互斥锁以确保线程安全,线程申请了互斥锁后,可以调用特定函数 进入条件变量等待队列(同时释放互斥锁) ,其他线程则可以通过条件变量在特定的条件下唤醒该线程( 唤醒后线

    2024年01月19日
    浏览(28)
  • 生产者消费者模型 C++ 版

    网上一般教程是使用std::queue,定义消费者 Consumer ,定义Producter类,在main函数里面加锁保证线程安全。 本片文章,实现一个线程安全的队列 threadsafe_queue,只在队列内部加锁。如此可适配,多生产者多消费者的场景 Consumer 头文件 cpp文件 运行结果如下: 优先队列做缓存 头文件

    2024年02月13日
    浏览(27)
  • 【设计模式】生产者消费者模型

    带你轻松理解生产者消费者模型!生产者消费者模型可以说是同步与互斥最典型的应用场景了!文末附有模型简单实现的代码,若有疑问可私信一起讨论。 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过

    2023年04月17日
    浏览(26)
  • 生产者消费者模型(基于go实现)

    基于 Channel 编写一个简单的单线程生产者消费者模型: 队列: 队列长度 10,队列元素类型为 int 生产者: 每 1 秒往队列中放入一个类型为 int 的元素,消费者: 每一秒从队列中获取一个元素并打印。 基于 Channel 编写一个简单的单线程生产者消费者模型: 队列: 队列长度

    2024年02月11日
    浏览(25)
  • 【Linux】深入理解生产者消费者模型

    生产者 - 消费者模型 Producer-consumer problem 是一个非常经典的多线程并发协作的模型,在分布式系统里非常常见。 在多线程开发中,如果生产者生产数据的速度很快,而消费者消费数据的速度很慢,那么生产者就必须等待消费者消费完了数据才能够继续生产数据,同理如果消费

    2024年02月06日
    浏览(26)
  • Java中的生产者/消费者模型

    生产者-消费者模型(Producer-Consumer problem)是一个非常经典的多线程并发协作的模型。 比如某个模块负责生产数据,而另一个模块负责处理数据。产生数据的模块就形象地被称为生产者;而处理数据的模块,则被称为消费者。 生产者和消费者在同一段时间内共用同一个存储空

    2024年02月07日
    浏览(34)
  • 【Linux】线程安全-生产者消费者模型

    1个线程安全的队列:只要保证先进先出特性的数据结构都可以称为队列 这个队列要保证互斥(就是保证当前只有一个线程对队列进行操作,其他线程不可以同时来操作),还要保证同步,当生产者将队列中填充满了之后要通知消费者来进行消费,消费者消费之后通知生产者

    2024年02月10日
    浏览(32)
  • 基于互斥锁的生产者消费者模型

    生产者消费者模型 是一种常用的 并发编程模型 ,用于 解决多线程或多进程环境下的协作问题 。该模型包含两类角色: 生产者和消费者 。 生产者负责生成数据 ,并将数据存放到共享的缓冲区中。 消费者则从缓冲区中获取数据 并进行处理。生产者和消费者之间通过共享的

    2024年02月12日
    浏览(32)
  • Linux——生产者消费者模型和信号量

    目录 ​​​​​​​ 基于BlockingQueue的生产者消费者模型 概念 条件变量的第二个参数的作用  锁的作用 生产者消费者模型的高效性 生产者而言,向blockqueue里面放置任务 消费者而言,从blockqueue里面拿取任务: 总结 完整代码(不含存储数据的线程) 完整代码(含存储线程)  信

    2024年02月07日
    浏览(32)
  • 基于 BlockQueue(阻塞队列) 的 生产者消费者模型

    阻塞队列(Blocking Queue) 是一种特殊类型的队列,它具有阻塞操作的特性。在并发编程中,阻塞队列可以用于实现线程间的安全通信和数据共享。 阻塞队列的 主要特点 是: 当 队列为空时 ,消费者线程尝试从队列中获取(出队)元素时会被阻塞,直到有新的元素被添加到队

    2024年02月12日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包