C++性能优化实践 三

C++性能优化实践 三

文章目录

        • 一、C++标准库线程间的通信
        • 二、内存屏障、获得与释放语义
        • 三、多线程优化总结

 书接上回, 这篇文章继续来谈谈C++ 并发编程性能优化相关的内容。

参考文章: hhttps://boolan.com/
先形象的说明一下并发与并行:
并发:类似与足球场踢足球, 大家为了抢一个球(数据)可能会发生碰撞, 足球在某一时刻只能在一个球员手里, 所以为了避免恶意抢球就需要裁判(数据同步), 最后球在谁手里由上帝(CPU)决定。
并行:类似于刘翔跨栏时的场景, 大运动员之间各自拼命跑, 互相之间不共享东西(数据)。

一、C++标准库线程间的通信

  这里首先纠正自己之前存在的一个知识点误区, 就是关于 std::condition_variable_any 的。先看如下代码:

void WorkFunc(int& result, std::condition_variable_any& cv, std::mutex& cv_mut, bool& ready_flag)
{
    result = 100;
    {
        std::unique_lock w_lock(cv_mut);
        ready_flag = true;
    }
    
    cv.notify_one();
    return;
}

int main()
{
    std::condition_variable_any cv;
    std::mutex cv_mut;
    bool ready_flag{false};
    int result{0};

    std::thread workth(std::ref(result), std::ref(cv), std::ref(cv_mut), std::ref(ready_flag));
    std::unique_lock r_lock(cv_mut);
    cv.wait(r_lock, [&ready_flag](){return ready_flag;});
    std::cout<<"result is:"<< result <<std::endl;
    
    if (workth.joinable()){
        workth.join();
    }
    
    return 0;
}       

 在早期我一直认为线程函数 WorkFunc() 内将 std::condition_variable_any cv 条件变量需要的 ready_flag 标志置为 true 是不需要加锁的, 当时以为条件变量的 wait() 函数会先循环查询 ready_flag 这个标志位为真后再去判断是否有信号量的通知, 所以就不用加锁, 看来还是太年轻了, 理解不到位。
 而实际情况是 wait() 函数先判断一次标志位, 然后就直接调用实际的 wait()。在复杂时序的情况下可能会出现在检测完 ready_flag 标志为 false 后, 线程函数先执行完了, 主线程再去调用实际的 wait(), 这个时候因为错过了通知就死锁了。 标志位的作用: ①避免错过 notify_one()通知; ②避免假醒。
 还有就是标准库线程类的构造函数都是值传参, 不会因线程函数的形参都是引用就推导出引用类型, 所有这个需要加上 std::ref

二、内存屏障、获得与释放语义

 为了保证多线程情况下对数据计算的正确性, 一方面当然是使用 std::mutex 来保证数据的同步, 此外, 某些数据较为简洁的应用场景则可以使用标准库提供的原子类型, 比如通知线程退出的标志位。 这里记录一下关于原子类型标准库提供的相关内存模型:

//保证当前语句之后的所有读写操作不乱序到当前语句之前
std::memory_order_acquire;

//保证当前语句之前的所有读写操作不乱序到当前语句之后
std::memory_order_release;

//松散类型, 只保证当前语句对数据操作的原子性, 没有任何内存屏障
std::memory_order_relaxed;

//同时拥有 std::memory_order_acquire 和 std::memory_order_release 的特点
std::memory_order_acq_rel;

//同时拥有 std::memory_order_acquire 和 std::memory_order_release 的特点外, 同时还拥有全局的内存顺序, 保证所有线程的执行顺序一致
std::memory_order_seq_cst   

关于内存序的典型应用场景就是单例模式里面创建实例时的双重检查锁定, 原始代码如下:

//线程非安全版本
Singleton* Singleton::getInstance() {
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

//线程安全版本, 但锁的代价过高
//当对象被创建后, 其他所有线程其实就不需要再等待持有锁了
Singleton* Singleton::getInstance() {
    Lock lock;
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}


//双检查锁, 但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
    
    if(m_instance==nullptr){
        Lock lock;
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
    }
    return m_instance;
}

双检查实现里面, 写代码时期望的执行顺序是先申请一块内存, 到后调用 Singleton() 构造函数, 最后将内存地址赋值给 m_instance。但是编译器出于优化的目的, 实际的顺序可能是 先申请一块内存, 然后将内存地址赋值给 m_instance, 最后再构造。 此时其他线程进函数后发现 m_instance 不为空, 然后直接返回, 此时这个实例是没有初始化的, 就可能会出现问题。所以安全的实现如下:

std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance.load(std::memory_order_relaxed);

    //获取内存fence
    std::atomic_thread_fence(std::memory_order_acquire);
    
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton;

            //释放内存fence
            std::atomic_thread_fence(std::memory_order_release);

            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}


//或者不用 fence 直接用获得释放语义实现
Singleton* Singleton::getInstance() {

    Singleton* tmp = m_instance.load(std::memory_order_acquire);
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton;
            m_instance.store(tmp, std::memory_order_release);
        }
    }
    return tmp;
}

这样就可以保证申请内存, 构造再赋值的执行顺序。

三、多线程优化总结

 首先需要知道的是, 多线程加锁和数据竞争是性能杀手。有以下几点需要注意:
①能用 std::atomic 原子类型就不要使用 std::mutex;
②如果多线程读比写多很多时, 优先考虑使用读写锁 std::shared_mutex, 其他情况还是使用 std::mutex;
③考虑使用 thread_local 变量, 这个相当于不需要加锁的全局变量, 当线程第一次访问的时候对象才会被创建, 线程退出时对象就会被销毁;

④能用标准库里面的高级接口就不要自己写, 比如 std::future, std::async等;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Flutter - 折叠面板

demo 地址: https://github.com/iotjin/jh_flutter_demo 代码不定时更新&#xff0c;请前往github查看最新代码 flutter 自定义折叠组件 支持三种类型和两种展示效果可自定义title和被折叠的内容 效果图 示例 import package:flutter/material.dart; import /jh_common/widge…

Faust勒索病毒:了解变种faust,以及如何保护您的数据

导言&#xff1a; 近年来&#xff0c;网络安全问题日益严峻&#xff0c;其中勒索病毒成为了一种日益猖獗的威胁。在众多勒索病毒中&#xff0c;.faust勒索病毒以其高度的隐秘性和破坏性引起了广泛关注。本文91数据恢复将深入剖析.faust勒索病毒的威胁特点&#xff0c;并提出相…

#ESP32S3N8R8(按键点灯)

一、按键对应端口为GPIO0&#xff08;上拉&#xff09; 二、代码 #include <stdio.h> #include "driver/gpio.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "unistd.h"void app_main(void) {int co…

JavaSE字节缓冲流

欢迎来到 请回答1024 的博客 &#x1f353;&#x1f353;&#x1f353;欢迎来到 请回答1024的博客 关于博主&#xff1a; 我是 请回答1024&#xff0c;一个追求数学与计算的边界、时间与空间的平衡&#xff0c;0与1的延伸的后端开发者。 博客特色&#xff1a; 在我的博客中&a…

ElasticSearch 安装(docker)

下载安装包 阿里云链接&#xff1a; elasticSearch.exe https://www.alipan.com/s/3A356NnmWaJ 提取码: 93da 点击链接保存&#xff0c;或者复制本段内容&#xff0c;打开「阿里云盘」APP &#xff0c;无需下载极速在线查看&#xff0c;视频原画倍速播放。 安装步骤 1、首先…

【介绍下OneFlow概念清单】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

INA226模块驱动代码-STM32F103

模块&#xff1a; 平台:STM32F103C8T6 标准库 软件模拟IIC C文件&#xff1a; #include "ina226.h"//delay static void delay_nns(uint16_t D) //30纳秒ns 根据手册要用到IIC的HS高速模式 {while(--D); }void delay_nms(uint16_t ms) //毫秒 {uint16_t i;uint3…

Android Dalvik虚拟机JNI方法的注册过程分析

Dalvik虚拟机在调用一个成员函数的时候&#xff0c;如果发现该成员函数是一个JNI方法&#xff0c;那么就会直接跳到它的地址去执行。也就是说&#xff0c;JNI方法是直接在本地操作系统上执行的&#xff0c;而不是由Dalvik虚拟机解释器执行。由此也可看出&#xff0c;JNI方法是A…

欧科云链:为什么减半对比特币生态的影响正在逐步“减弱”?

出品&#xff5c;OKG Research 作者&#xff5c;Jason Jiang 欧科云链OKLink数据显示&#xff0c;比特币于区块高度840000&#xff08;北京时间2024年4月20日8:09&#xff09;成功完成第四次减半&#xff0c;比特币挖矿奖励正式由6.25BTC减少至3.125BTC。此次减半之后&#x…

微信小程序:11.本地生活小程序制作

开发工具&#xff1a; 微信开发者工具apifox进行创先Mock 项目初始化 新建小程序项目输入ID选择不使用云开发&#xff0c;js传统模版在project.private.config中setting配置项中配置checkinalidKey&#xff1a;false 梳理项目结构 因为该项目有三个tabbar所以我们要创建三…

Mysql_数据库事务

文章目录 &#x1f60a; 作者&#xff1a;Lion J &#x1f496; 主页&#xff1a; https://blog.csdn.net/weixin_69252724 &#x1f389; 主题&#xff1a; MySQL__事务&#xff09; ⏱️ 创作时间&#xff1a;2024年04月26日 ———————————————— 这里写目…

STM32、GD32驱动SHT30温湿度传感器源码分享

一、SHT30介绍 1、简介 SHT30是一种数字湿度和温度传感器&#xff0c;由Sensirion公司生产。它是基于物理蒸发原理的湿度传感器&#xff0c;具有高精度和长期稳定性。SHT30采用I2C数字接口&#xff0c;可以直接与微控制器或其他设备连接。该传感器具有低功耗和快速响应的特点…

Pytorch 的神经网络 学习笔记

参照官方网址 Module — PyTorch 2.2 documentation 一. 介绍 1. torch.nn模块&#xff1a; torch.nn是PyTorch中专门用于构建神经网络的模块。它提供了构建深度学习模型所需的所有构建块&#xff0c;包括各种层类型&#xff08;如全连接层、卷积层、循环层等&#xff09;、…

笔记本硬盘坏了怎么把数据弄出来 笔记本硬盘数据恢复一般需要多少钱

现在办公基本都离不开笔记本电脑&#xff0c;就连学生写作业也大多是都在电脑上完成。硬盘作为电脑存储的重要组成部分&#xff0c;承载着存储文件和各类软件的重任。如果硬盘出现故障&#xff0c;基本上这台电脑就无法正常工作&#xff0c;同时我们可能面临丢失很多重要的数据…

js字符串方法总结_js 字符串方法(1)

var count0 var prosstr.indexOf(a) while(pros!-1) {countprosstr.indexOf(a,pros1) } console.log(count);3. chartAt() 返回指定位置的字符 根据下标获取字符var strabcdef console.log(str.charAt(2));4. lastIndexOf() 返回字符串字串出现的最后一处出现的位置索引 没有匹…

Hadoop之路

hadoop更适合在liunx环境下运行&#xff0c;会节省后期很多麻烦&#xff0c;而用虚拟器就太占主机内存了&#xff0c;因此后面我们将把hadoop安装到wsl后进行学习,后续学习的环境是Ubuntu-16.04 &#xff08;windows上如何安装wsl&#xff09; 千万强调&#xff0c;有的命令一…

架构师系列-Docker(一)-基础及MYSQL安装

轻量容器引擎Docker Docker是什么 Docker 是一个开源项目&#xff0c;诞生于 2013 年初&#xff0c;最初是 dotCloud 公司内部的一个业余项目。 它基于 Google 公司推出的 Go 语言实现&#xff0c;项目后来加入了 Linux 基金会&#xff0c;遵从了 Apache 2.0 协议&#xff0c;…

高级控件5-RecyclerView

与ViewPager类似的一个滑动的高级控件是RecyclerView&#xff0c;使用更加灵活。 第1步&#xff1a;添加依赖 打开mvn官网&#xff0c;检索recyclerview&#xff0c;选择使用人数较多的版本&#xff0c;复制依赖&#xff0c;放入项目中即可 快捷方法&#xff08;复制下面的代…

【Qt】信号与槽

1 &#x1f351;信号和槽概述&#x1f351; 在 Qt 中&#xff0c;用户和控件的每次交互过程称为⼀个事件。⽐如 “⽤⼾点击按钮” 是⼀个事件&#xff0c;“⽤⼾关闭窗⼝” 也是⼀个事件。每个事件都会发出⼀个信号&#xff0c;例如⽤⼾点击按钮会发出 “按钮被点击” 的信号&…

LabVIEW 2024安装教程(附免费安装包资源)

鼠标右击软件压缩包&#xff0c;选择“解压到LabVIEW.2024”。 返回解压后的文件夹&#xff0c;鼠标右击“ni_labview-2024”选择“装载”。 鼠标右击“Install”选择“以管理员身份运行”。 点击“我接受上述2条许可协议”&#xff0c;然后点击“下一步”。 点击“下一步”。 …
最新文章