C++深度优化——无锁队列实现及测试

news2024/12/26 10:40:40

最近在研究无锁队列,从网上学习到了lock-free的定义,特此摘录如下:

如果涉及到共享内存的多线程代码在多线程执行下不可能互相影响导致被hang住,不管OS如何调度线程,至少有一个线程在做有用的事,那么就是lock-free。

摘录自:C++ memory order循序渐进(一)—— 多核编程和memory model_c++ 多核编程-CSDN博客

我总结:lock-free其实就是存在一种可回滚的机制,在尝试失败后恢复到最新的状态继续尝试。

网上有很多无锁队列的实现,看了几个,发现都是有BUG的,也包括我之前写的关于无锁队列的实现。比如:

多线程---解析无锁队列的原理与实现_多线程无锁队列_攻城狮百里的博客-CSDN博客

总结了一下这些实现的最根本问题:在队列中只有一个元素的时候,在弹出该元素的同时,push可能正在修改该元素的next成员。也就是说我们的计算机现在没有一个原子操作在验证尾指针没变的同时修改其成员。现在的原子操作能做到的只是在确认指针没变的时候修改指针本身。

于是我实现了一个目前看上去没有bug的单生产者单消费者的队列,如下:

#include <atomic>
template<typename val_t>
class lock_free_queue
{
private:
    struct node_t
    {
        val_t v;
        std::atomic<node_t*> next;

        node_t():v(), next(nullptr){}
        node_t(const val_t& v_):v(v_), next(nullptr)
        {}
    };
    std::atomic<node_t*> m_head, m_tail;
    std::atomic<bool> m_tail_changed;
public:
    lock_free_queue():m_head(new node_t()), m_tail(m_head.load()), m_tail_changed(false)
    {}

    void push(const val_t& v)
    {
        node_t* p_newnode = new node_t(v);      //* 创建一个新的对象 
        node_t* pnullptr = nullptr,*p_oldtail = nullptr;
        do
        {
            pnullptr = nullptr;
            p_oldtail = m_tail.load();          //* 这里如果挂起,下面的p_oldtail->next就会崩溃
			std::atomic_thread_fence(std::memory_order_acquire);
            if ( !m_tail_changed.load() && p_oldtail->next.compare_exchange_strong(pnullptr, p_newnode))
            {
                break;
            }
            m_tail_changed.store(false, std::memory_order_release);
        }while (true);
        if (!m_tail.compare_exchange_strong(p_oldtail, p_newnode)) ;
    }

    bool pop(val_t& v)
    {
        node_t* p_oldhead = m_head.load()->next.load();
        //* 取出头
        while (p_oldhead && !m_head.load()->next.compare_exchange_strong(p_oldhead, p_oldhead->next.load()))
        {}
        if (p_oldhead == nullptr)return false;          //* 列表为空
		node_t* p_oldheadbak = p_oldhead;
        //* 控制取出最后一个元素后尾指针的位置,但是tail取出并销毁后push如果之前已经保存了tail就会失效
		if (m_tail == p_oldhead) 
		{
			m_tail_changed.store(true, std::memory_order_relaxed);                  //* 这里可能会对末尾修改,先置标志让为为指针判断失效
			std::atomic_thread_fence(std::memory_order_release);
			if (!m_tail.compare_exchange_strong(p_oldhead, m_head.load(), std::memory_order_acq_rel))
				m_tail_changed.store(false, std::memory_order_release);     //* 假设修改尾指针成功了,但是未来得及置标志
		}
        if (p_oldheadbak)
        {
            v = p_oldheadbak->v;
            delete p_oldheadbak;
        }
        return true;
    }
};

这个实现引入了一个新的类成员m_tail_changed用以表示尾指针是否变化。但分析来看,这实际上就是一个自旋锁,在pop函数中如果可能修改尾指针先置上该标志以保证m_tail_changed==true是sequence-before m_tail修改的。这样在push中如果发现m_tail_changed标志被置上就需要等待尾指针更新完毕。当然,这种实现确实是违背了lock-free的原则,因为自旋锁的存在会导致全局的阻塞,虽然这种情况只有在弹出最后一个元素的时候才会存在。

接下来展示一下测试情况:

#include <thread>
#include <atomic>
#include <future>
#include <string>
#include <iostream>
#include <chrono>
#include "lock_free_queue.hpp"
#include "lock_queue.hpp"

using namespace std;

#define MAX_LOOP_CNT 100000

int main(int argc, char**argv)
{

    do
    {
        cout << "lock free" << endl;
        lock_free_queue<int> lfq;
		
        thread producer([&]()
            {
				int v = 0;
				int i = 0;
                while (i++ < MAX_LOOP_CNT)
                {
                    lfq.push(v++);
                }
            });
		int s = 0, f = 0;
        thread consumer([&]()
            {
			//std::this_thread::sleep_for(std::chrono::nanoseconds(100));
				
                int vpop = 0;
				int i = 0;
                while (i++ < MAX_LOOP_CNT)
                {
                    if (lfq.pop(vpop))
                    s++;
                    else
                    f++;
                }
            });
		auto begin = std::chrono::high_resolution_clock::now();
        producer.join();consumer.join();
		auto end = std::chrono::high_resolution_clock::now();
		auto du = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
		cout << "s:" << s << " f:" << f << " t:" << du.count() << endl;
    }while(0);
    
    do
    {
        cout << "lock" << endl;
        volatile bool brun = true;
        lock_queue<int> lfq;
		thread producer([&]()
		{
			int v = 0;
			int i = 0;
			while (i++ < MAX_LOOP_CNT)
			{
				lfq.push(v++);
			}
		});
		int s = 0, f = 0;
		thread consumer([&]()
		{
			//std::this_thread::sleep_for(std::chrono::nanoseconds(100));

			int vpop = 0;
			int i = 0;
			while (i++ < MAX_LOOP_CNT)
			{
				if (lfq.pop(vpop))
					s++;
				else
					f++;
			}
		});
		auto begin = std::chrono::high_resolution_clock::now();
		producer.join(); consumer.join();
		auto end = std::chrono::high_resolution_clock::now();
		auto du = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
		cout << "s:" << s << " f:" << f << " t:" << du.count() << endl;
    }while(0);
    return 0;
}

查看一下有锁和无锁情况下执行100000次插入和弹出的情况。在windows x64系统上,使用debug模式得到的结果如下:

无锁队列插入10^5次时间是有锁队列的一半。但是在release情况下:

有锁队列和无锁队列效率相当,甚至有锁队列的效率要略高于无锁队列。这有可能是无锁队列在内部需要创建node节点导致的。

下面是CentOS7.9系统中使用-g时候的情况:

无锁队列的效率也是要比有锁队列快一倍的。然后再试试-O2优化的结果:

这个效率,只能说相当,无锁略快,但是如果再使用内存池进行优化应该会更好一些。

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

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

相关文章

计网第五章(运输层)(七)(TCP的连接建立)

目录 一、基本概述 二、连接建立 1.基本任务 2.具体实现 三、经典问题之为什么不用“两次握手”&#xff1f; 一、基本概述 在前面的部分提到过&#xff0c;TCP是基于运输连接来传输TCP报文段。 所以TCP的连接和释放是每次面向连接的通信过程中必不可少的过程。 TCP运输…

Linux设备驱动模型之platform设备

Linux设备驱动模型之platform设备 上一章节介绍了Linux字符设备驱动&#xff0c;它是比较基础的&#xff0c;让大家理解Linux内核的设备驱动是如何注册、使用的。但在工作中&#xff0c;个人认为完全手写一个字符设备驱动的机会比较少&#xff0c;更多的都是基于前人的代码修修…

Vue3+移动端适配屏幕+默认横屏展示

效果图展示区: 1. 想要把px自动转换单位为vw需要项目根目录.postcssrc.js中进行配置以下代码 module.exports {plugins: {autoprefixer: {}, // 用来给不同的浏览器自动添加相应前缀&#xff0c;如-webkit-&#xff0c;-moz-等等"postcss-px-to-viewport": {unitTo…

C/C++计算表达式的值 2023年5月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C计算表达式的值 一、题目要求 1、编程实现 2、输入输出 二、解题思路 1、案例分析 三、程序代码 四、程序说明 五、运行结果 六、考点分析 C/C计算表达式的值 2023年5月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 给定整数x,y的值&#xff0c…

视频号挂公众号链接引流到公众号还能加,好消息来了

视频号挂公众号链接要求在八月初出来了新规则&#xff0c;相信玩视频号的人大家都应该清楚&#xff0c;这两个新规则第一个看似简单&#xff0c;但是第二个却堵住了99%的人 接着看看视频号挂链接发展的来龙去脉 要点一&#xff1a;早在前两年&#xff0c;视频号链接直接显示在视…

基于springboot的流沐商城的设计与实现(前后端分离)

博主主页&#xff1a;一季春秋博主简介&#xff1a;专注Java技术领域和毕业设计项目实战、Java、微信小程序、安卓等技术开发&#xff0c;远程调试部署、代码讲解、文档指导、ppt制作等技术指导。主要内容&#xff1a;毕业设计(Java项目、小程序等)、简历模板、学习资料、面试题…

【JavaSE笔记】运算符

一、前言 作为Java编程中最基本的语法元素之一&#xff0c;运算符在编写程序时扮演着至关重要的角色。 运算符被用于执行各种数学和逻辑运算&#xff0c;以及比较操作&#xff0c;运算符的使用可以使代码更加简洁、易读和高效。在本文中&#xff0c;我们将会详细介绍Java中常…

nginx反向代理,用户访问服务器1的80端口,请求转发至服务器2,3的8882端口

两台应用服务器&#xff0c;一台nginx&#xff0c;用户访问nginx服务器80端口&#xff0c;将请求转发至服务器2和服务器3的8882端口。 1、修改nginx配置文件 upstream backend {server 10.60.16.187:8882;server 10.60.16.188:8882;}server {listen 80;server_name 10.6…

Git(9)——Git多人协同开发之创建初始项目

目录 一、简介 二、创建新项目 三、本地Git接入 四、创建远端仓库——Gitee 五、推送代码至远端仓库 一、简介 前面8章都是我们一个人独自开发&#xff0c;如果现在项目新增两名同事&#xff0c;我们就需要使用Git来实现多人协同开发&#xff0c;从第九章开始将介绍从零到…

Atlas VPN 曝零日漏洞,允许查看用户真实 IP 地址

Atlas VPN 已确认存在一个零日漏洞&#xff0c;该漏洞允许网站所有者查看 Linux 用户的真实 IP 地址。不久前&#xff0c;发现该漏洞的人在Reddit上公开发布了有关该零日漏洞的详细信息以及漏洞利用代码。 关于 Atlas VPN 零日漏洞 Atlas VPN提供 "免费 "和付费的 …

GemBox.Bundle 47.0.1227 Crack

GemBox.Document 35.0.1480 GemBox.Email 17.0.1147 GemBox.Imaging 10.0.1096 GemBox.Pdf 17.0.1404 GemBox.Presentation 25.0.1526 GemBox.Spreadsheet 49.0.1454 GemBox.Spreadsheet 从 .NET 应用程序读取、写入、转换和打印 XLSX、XLS、XLSB、CSV、HTML 和 ODS 电子表…

算法题笔记 1-5

目录 week 11. 找出数组中重复的数字题目数据范围样例题解(数组遍历) O(n) 2. 不修改数组找出重复的数字题目数据范围样例题解(分治&#xff0c;抽屉原理) O(nlogn) 3. 二维数组中的查找题目题解(单调性扫描) O(nm) 4.替换空格题目题解(线性扫描) O(n)(双指针扫描) O(n) 5.从尾…

一阶滞后低通滤波器(支持采样频率设置 博途SCL代码)

一阶低通滤波器算法介绍这篇博客不再赘述&#xff0c;专栏有很多的文章讲过。之前的低通滤波器都是没有采样频率接口的&#xff0c;低通滤波器的采样频率都等于定时中断周期&#xff0c;实际滤波效果和信号采样频率、滤波系数、信号采样频率都有关系&#xff0c;所以这里我们将…

【C语言】指针的进阶(二)—— 回调函数的讲解以及qsort函数的使用方式

目录 1、函数指针数组 1.1、函数指针数组是什么&#xff1f; 1.2、函数指针数组的用途&#xff1a;转移表 2、扩展&#xff1a;指向函数指针的数组的指针 3、回调函数 3.1、回调函数介绍 3.2、回调函数的案例&#xff1a;qsort函数 3.2.1、回顾冒泡排序 3.2.1、什么是qso…

Python 10之异常模块包

&#x1f600;前言 在Python编程中&#xff0c;我们时常会遇到各种异常和错误&#xff0c;同时我们也会使用多个模块和包来组织和结构化我们的代码。理解如何有效地处理异常和组织我们的代码是成为一个成功的Python程序员的关键。 . 在本教程中&#xff0c;我们将深入探讨Pytho…

10.3 滤波电路

整流电路的输出电压虽然是单一方向的&#xff0c;但是含有较大的交流成分&#xff0c;不能适应大多数电子电路及设备的需要。因此&#xff0c;一般在整流后&#xff0c;还需利用滤波电路将脉动的直流电压变为平滑的直流电压。与用于信号处理的滤波电路相比&#xff0c;直流电源…

Friend.tech和Tip Coin爆火!去中心化社交热度再起?

在Web2.0时代&#xff0c;用户对于大型中心化社交平台的信任逐渐降低&#xff0c;于是&#xff0c;去中心化的Web3社交应用也开始如雨后春笋般冒出。其中&#xff0c;像Friend.tech和Tip Coin这样的项目一经推出便在Twitter等平台刷爆了热榜。 Friend.tech基于Coinbase Layer 2…

SAP FI之自动付款程序运行 F110

简介 付款流程包括以下步骤 输入发票分析未结发票的到期日准备应付发票付款被批准或修改发票已付款 始终需要处理大量的发票。 必须按时支付应付帐款发票才能获得可能的折扣。 会计部门希望自动执行此发票处理。 自动付款程序是一种可以帮助用户管理应付帐款的工具。 SAP 为用…

Python 自定义模块

视频版教程 Python3零基础7天入门实战视频教程 Python中已经有很多的内置模块&#xff0c;以及也有很多的第三方优秀模块&#xff0c;我们直接导入使用即可。 当然我们有时候也需要自己定义一些自定义模块&#xff0c;来实现我们项目的功能。 看下案例&#xff1a; 先定义s…

基于Java的大学生在线租房平台的设计与实现(亮点:合理的租房流程、房屋报修、多角色、在线评论回复)

校园点餐小程序 一、前言二、我的优势2.1 自己的网站2.2 自己的小程序&#xff08;小蔡coding&#xff09;2.3 有保障的售后2.4 福利 三、开发环境与技术3.1 MySQL数据库3.2 Vue前端技术3.3 Spring Boot框架3.4 微信小程序 四、功能设计4.1 主要功能描述 五、系统实现5.1 前面界…