C++编程:生产者-消费者模型中条件变量的使用问题及优化方案

news2024/12/25 9:20:06

文章目录

    • 0. 引言
    • 1. 生产者-消费者模型简介
      • 1.1 示例代码
      • 1.2 为什么必须加锁?
    • 2. 上述代码存在的问题
      • 2.1 信号丢失
      • 2.2 锁的作用范围
      • 2.3 竞态条件
    • 3. 优化方案
      • 3.1 使用两个条件变量
      • 3.2 扩展锁的作用域
      • 3.3 使用原子操作
      • 3.4 使用无锁队列
    • 4. 底层实现与深入探讨
    • 5. 流程图解析
    • 6. 结论

0. 引言

在C++多线程编程中,生产者-消费者模型是一种常见的并发模式。然而,由于我们项目中不当的设计导致消费者线程偶尔处于永远等待状态。本文将探讨这一现象的原因,并提出相应的解决方案。

1. 生产者-消费者模型简介

生产者-消费者模型涉及两个主要角色:

  • 生产者:负责生成数据并将数据放入一个共享队列中。
  • 消费者:从队列中取出数据并进行处理。

1.1 示例代码

这里给出一个基本的生产者-消费者模型示例,其中使用了 std::mutexstd::condition_variable 来同步生产者和消费者之间的交互。

#include <queue>
#include <mutex>
#include <condition_variable>

std::mutex mut;
std::queue<int> data_queue;
std::condition_variable data_cond;

void producer_thread() {
  while (true) {
    int data = generate_data(); // 生产数据
    {
      std::lock_guard<std::mutex> lck(mut);
      data_queue.push(data); // 仅在操作队列时加锁
    }
    data_cond.notify_one(); // 通知消费者线程
  }
}

void consumer_thread() {
  while (true) {
    std::unique_lock<std::mutex> lck(mut);
    data_cond.wait(lck, []{ return !data_queue.empty(); }); // 等待直到队列中有数据
    int data = data_queue.front(); // 获取队列中的数据
    data_queue.pop(); // 从队列中移除数据
    lck.unlock(); // 解锁
    process_data(data); // 处理数据
  }
}

1.2 为什么必须加锁?

在生产者-消费者模型中,加锁是必须的,因为共享资源(如队列)在多线程环境下需要受到保护,以避免数据竞争、竞态条件等问题。然而,加锁确实会对性能产生影响,因此在实际开发中,优化锁的使用成为关键。

  • 数据一致性:如果生产者和消费者同时访问共享队列,没有加锁会导致数据损坏或丢失。例如,生产者在向队列推送数据时,消费者可能正在从队列读取数据。如果没有加锁,可能会导致读取到无效数据或程序崩溃。

  • 防止竞态条件:竞态条件是指程序的结果依赖于多个线程的执行顺序。在没有加锁的情况下,线程的执行顺序是不可预测的,从而导致不确定的行为。

2. 上述代码存在的问题

2.1 信号丢失

如果 notify_one() 在消费者线程进入 wait() 之前被调用,通知信号可能会被丢失,导致消费者线程无限等待。

2.2 锁的作用范围

notify_one() 没有被包含在锁的作用范围内,这可能导致竞态条件,甚至导致死锁。

2.3 竞态条件

条件变量的使用容易出现竞态条件,导致通知无法及时响应。

3. 优化方案

3.1 使用两个条件变量

为了解决信号丢失问题,可以引入两个条件变量:start_conditionend_condition,分别用于数据生产和处理的不同阶段。

std::mutex mut;
std::queue<int> data_queue;
std::condition_variable start_condition;
std::condition_variable end_condition;

void producer_thread() {
  while (true) {
    int data = generate_data();
    std::lock_guard<std::mutex> lck(mut);
    data_queue.push(data);
    start_condition.notify_one();
  }
  std::lock_guard<std::mutex> lck(mut);
  end_condition.notify_all();
}

void consumer_thread() {
  while (true) {
    std::unique_lock<std::mutex> lck(mut);
    start_condition.wait(lck, []{ return !data_queue.empty(); });
    int data = data_queue.front();
    data_queue.pop();
    lck.unlock();
    process_data(data);
    if (is_last_data(data)) {
      std::lock_guard<std::mutex> lck(mut);
      end_condition.notify_one();
      break;
    }
  }
}

3.2 扩展锁的作用域

为避免信号丢失和竞争条件,notify_one() 应在持有锁的情况下调用:

std::unique_lock<std::mutex> lck(mut);
start_condition.notify_one();

确保所有对共享资源的操作都在锁的保护下进行,可以提升系统的健壮性。

3.3 使用原子操作

对于任务计数和状态管理,原子操作可以确保数据一致性,减少竞态条件的发生。例如:

std::atomic<bool> all_data_processed = false;

3.4 使用无锁队列

无锁队列的设计目的是在不使用互斥锁(mutex)的情况下实现多线程间的通信。它依赖于原子操作来保证线程安全。原子操作可以确保读取、修改和写入数据的过程不可中断,从而避免了使用锁带来的额外开销。

详细请查看 C++编程:无锁环形队列 (LockFreeRingQueue)的简单实现、测试和分析

4. 底层实现与深入探讨

操作系统层面,条件变量通常基于 pthread 实现。以下是一个简化的底层信号处理实现示例,展示了如何确保条件变量的信号不会丢失,并能安全地唤醒等待中的线程。

int _pthread_cond_signal(pthread_cond_t *cond) {
    unsigned int wrefs = atomic_load_relaxed(&cond->_data._wrefs);
    if (wrefs >> 3 == 0)
        return 0;
    int private = _condvar_get_private(wrefs);
    _condvar_acquire_lock(cond, private);
    unsigned long long int wseq = _condvar_load_wseq_relaxed(cond);
    bool do_futex_wake = false;
    if ((cond->_data._g_size[gl] != 0) || _condvar_quiesce_and_switch_gl(cond, wseq, &gl, private))
        atomic_fetch_add_relaxed(cond->_data._g_signals + gl, 2);
    _condvar_release_lock(cond, private);
    if (do_futex_wake)
        futex_wake(cond->_data._g_signals + gl, 1, private);
    return 0;
}

5. 流程图解析

以下是生产者-消费者模型的执行流程图,帮助理解各步骤之间的关系:

流程图

6. 结论

通过对生产者-消费者模型中条件变量的优化,可以有效避免信号丢失和死锁问题。合理设计锁的作用范围、使用多个条件变量、利用原子操作进行同步,能够大大提高并发程序的稳定性。在实际开发中,务必结合具体需求和环境,选择合适的优化方案。

虽然加锁会影响性能,但通过减少锁的粒度、使用无锁数据结构、批量处理等方式,可以显著降低这种影响。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2047791.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

『 C++ 』IO流

文章目录 IO流概述iostream 的标准对象C流和C标准库I/O函数的同步 sync_with_stdiofstream 文件流文件流的打开标志二进制读写二进制读写的浅拷贝问题文本读写 字符串流注意 IO流概述 流是指数据的有序传输序列,路表示数据从一个地方流向另一个地方的过程,流可以是输入流也可以…

欧盟新规:苹果App Store开发者需公开联系方式,透明度提升还是隐私挑战?

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 随着数字经济的蓬勃发展&#xff0c;欧盟对数字服务的监管也在不断加强。最近&#xff0c;苹果公司宣布了一项针对欧盟App Store的新政策&#…

Lesson 67 The weekend

Lesson 67 The weekend 词汇 greengrocer 菜市场 构成&#xff1a;green n. 绿色的    grocer n. 食杂店&#xff0c;小卖店 商店词汇&#xff1a;shop n. 商店      store n. 小店      market n. 市场      super market 超市      Sunday market 二…

Codeforces Round 949 (Div. 2) C.D构造和E题

C题链接 D题链接 E题链接 C题思路&#xff1a; 我们设相邻的两个-1的位置是的值是l和r&#xff0c;他们直接的距离是d(也就是r的下标减l的下标)。 思路1&#xff1a;直接模拟操作&#xff0c;看所有操作里是否有合法操作。 比如1 -1 -1 -1 -1 -1 7. 容易想到1*213,3*217&a…

psychopy 中文语义相关判断任务实验设计

参考文献&#xff1a; [石如彬, 谢久书, 杨梦情, & 王瑞明. (2022). 语言和情境对具体概念感知运动仿真的影响. 心理学报, 54(6), 583–594. https://doi.org/10.3724/SP.J.1041.2022.00583] 2.2.4实验1。 演示效果 按下“上方向键” 按F或J 反馈信息&#xff1a; 实验步骤…

C#中的S7协议

S7协议-S7COMM S7COMM 进行写 CTOP->PDU type已知枚举值 0X0E连接请求0x0d连接确认0x08断开请求0x0c断开确认0x05拒绝访问0x01加急数据0x02加急数据确认0x04用户数据0x07TPDU错误0x0f数据传输 S7Header->ROSCTR已知枚举值 0X01JOB REQUEST。主站发送请求0x02Ack。从站…

jmeter压测websocket

1、jmeter安装websocket插件 下载地址 pjtr / JMeter WebSocket Samplers / Downloads — Bitbucket 下载之后&#xff0c;放到lib/ext文件夹下&#xff0c;重启jmeter即可&#xff0c;看到下图这些证明插件安装成功 2、脚本 新建websocket request-response sampler

day05-SpringBootWeb请求响应学习笔记

上面说过&#xff0c;浏览器向服务端发送请求&#xff0c;服务端会给浏览器发送出响应&#xff0c;无论是哪种&#xff0c;都包含三部分。这一章&#xff0c;依旧围绕这部分内容 请求 Postman 由于前后端分离&#xff0c;对我们后端技术人员来讲&#xff0c;在开发过程中&…

SpringBoot——整合Swagger

目录 Swagger Swagger工具集 Swagger注解 项目总结 新建SpringBoot项目 pom.xml Swagger2Config配置类 User实体类 UserController控制器 项目测试 添加用户 修改用户 查询用户 删除用户 Swagger Swagger是一款基于RESTful接口的用于文档在线自动生成和功能测试的开…

揭开虚拟与现实的帷幕:二进制世界与道

本章将带领读者进入一个结合科学与哲学的思维世界&#xff0c;从一个全新的视角探讨二进制世界的概念&#xff0c;结合超弦理论和老子的“道”哲学&#xff0c;深入理解计算机底层的运行原理及其与宇宙本质的联系。通过回顾经典电影《黑客帝国》以及最新的人工智能发展&#xf…

Android经典实战之Kotlin 2.0 迁移指南:全方位优化与新特性解析

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 Kotlin 2.0 迁移指南&#xff1a;开发者如何迎接新时代 Kotlin 2.0&#xff0c;这个备受期待的版本&#xff0c;终于在 JetBrains 的精心打磨下…

前端各种文本文件预览 文本编辑excel预览编辑 pdf预览word预览 excel下载pdf下载word下载

前端各种文本文件预览 文本编辑excel预览编辑 pdf预览word预览 excel下载pdf下载word下载 各种文本文件预览&#xff08;pdf, xlsx, docx, cpp, java, sql, py, vue, html, js, json, css, xml, rust, md, txt, log, fa, fasta, tsv, csv 等各种文本文件&#xff09; 其中 除p…

【LeetCode Cookbook(C++ 描述)】一刷二叉树综合(上)

目录 LeetCode #226&#xff1a;Invert Binary Tree 翻转二叉树「遍历」「分而治之」广度优先搜索&#xff1a;层序遍历 LeetCode #101&#xff1a;Symmetric Tree 对称二叉树递归法迭代法 LeetCode #100&#xff1a;Same Tree 相同的树递归法迭代法 LeetCode #559&#xff1a;…

万能钥匙:解锁 C++ 模板的无限可能

1.泛型编程 1.1:交换两个数(C语言) 1.2:交换两个数(C) 1.3:泛型编程 2:函数模板 2.1:函数模板的概念 2.2:函数模板的格式 ​编辑 2.3:函数模板的原理 2.4:模板的实例化 2.4.1:隐式实例化 2.4.2:显式实例化:在函数名后的<>中指定模板参数的实际类型. 2.4.2.1…

Unidbg使用指南

Unidbg使用指南 简介使用Unidbg补环境仅含C语言C调用 Java 实操——车智赢在unidbg实现执行so中的方法附——关于引用数据类型的转换附——静态注册和动态注册模板静态注册动态注册 现在很多的app使用了so加密&#xff0c;以后会越来越多。爬虫工程师可能会直接逆向app&#xf…

黑马前端——days09_css

案例 1 页面框架文件 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X-UA-Compati…

Ubuntu20.04如何安装配置JDK

资源准备 官方下载地址&#xff08;根据自己的系统版本选择不同版本进行下载即可&#xff09;&#xff1a;Java Downloads | Oracle 如无特殊需要可直接移步至下方JDK1.8安装包 https://download.csdn.net/download/qq_43439214/89646731 安装步骤 创建Java目录 sudo mkdir …

jmeter安装及环境变量配置、Jmeter目录介绍和界面详解

一 JMeter简介 Apache JMeter是100%纯JAVA桌面应用程序&#xff0c;被设计为用于测试客户端/服务端结构的软件(例如web应用程序)。它可以用来测试静态和动态资源的性能&#xff0c;例如&#xff1a;静态文件&#xff0c;Java Servlet,CGI Scripts,Java Object,数据库和FTP服务器…

【已解决】在进行模型量化推理的过程中遇到的错误以及解决方法

①在使用vLLM推理模型时&#xff0c;出现&#xff1a; Error in calling custom op rms_norm: _OpNamespace _C object has no attribute rms_norm 尝试众多解决方法之后&#xff0c;包括重新安装 pip install vllm0.5.0 对我有用的解决方法&#xff1a; 修改子目录下的vll…

【2024最新】Windows系统上NodeJS安装及环境配置图文教程

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境&#xff0c;允许在服务器端运行 JavaScript 代码。它采用事件驱动、非阻塞 I/O 模型&#xff0c;非常适合构建高性能的网络应用程序。Node.js 提供了一系列内置模块&#xff0c;支持异步编程&#xff0c;易于扩展&…