一文带你彻底理解高性能无锁队列

2024-04-21 16:48

本文主要是介绍一文带你彻底理解高性能无锁队列,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一文带你彻底理解高性能无锁队列

目前,大部分软件设计都在追求高性能,快速处理,耗时低,仿佛已经是行业中必不可少的一部分。作为互联网从业人员,我们也必须适应时代的潮流,彻底掌握这种高性能编程。


问题引入:

一个生产者,多个消费者的队列,如果是你,你回怎么设计?

想必拿到这个问题,更多的人脑海中已经浮现了一把锁;我也是的,那我们就从浅入深的来看看高性能的无锁队列是怎么一步一步的演化开来的。


1、低效的实现队列

编写多线程的时候,往往会发生资源竞争的现象,导致我们不得不加锁去保护变量,但在这个的同时也对性能造成了一定的损耗。

设计方案:(C++)

如图所示:

在这里插入图片描述

这种设计方式的话,我们是不可避免加锁操作的,因为其本身就不是线程安全的。

流程:

生产者放入队列中时,加锁,数据输入后完成加锁操作;然后剩余线程进行争锁操作,进行取队列数据操作;

简单实现:

template<class T>
class SimpleQueue
{
public:SimpleQueue() {}~SimpleQueue(){}void Push(T val){_mutex.lock();_q.push(move(val));_mutex.unlock();}T Get(){_mutex.lock();if (_q.empty()){_mutex.unlock();return 0;}T val = _q.front();_q.pop();_mutex.unlock();return val;}private:mutex _mutex;queue<T>_q;
};

这个就是比较简单,同事性能较差的一种方案;

既然我们提到了高性能,那么这种操作是不不符合我们需求的,那还有什么更好的方案提供跟高的性能吗?

很显然的一个操作:去锁化-----也就是常说的无锁队列


2、无锁队列

其实有锁和无锁就是我们平时所说的乐观锁和悲观锁:

   加锁是一种悲观的策略,它总是认为每次访问共享资源的时候,总会发生冲突,所以宁愿牺牲性能(时间)来保证数据安全。无锁是一种乐观的策略,它假设线程访问共享资源不会发生冲突,所以不需要加锁,因此线程将不断执行,不需要停止。一旦碰到冲突,就重试当前操作直到没有冲突为止。

无锁的策略使用一种叫做比较交换的技术(CAS Compare And Swap)来鉴别线程冲突,一旦检测到冲突产生,就重试当前操作直到没有冲突为止。

CAS是系统原语,CAS操作是一条CPU的原子指令,所以不会有线程安全问题。

CAS 的伪码:

template <class T>
bool CAS(T* addr, T expected, T value) 
{if (*addr == expected) {*addr = value;return true;}return false;
} 

CASexpected 与一个内存地址进行比较,如果比较成功,就将内存内容替换为 new 。当前大多数机器都在硬件级实现了这个操作,在 Inter 处理器上这个操作是 CMPXCHG ,因而 CAS 是一个最基础的原子操作。

GCC4.1+版本中支持CAS的原子操作,API接口如下:

**bool** __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)
#include <algorithm>
#include <vector>
#include <string>
#include <unordered_map>
#include <queue>
#include <functional>
#include <stack>
#include <iostream>
#include <unistd.h>
#include <thread>
#include <list>using namespace std;/*
*   说明:基于CAS封装的无锁List。
*/
template <typename T>
class JzLockfreeList
{
private:std::list<T> list;private:int mutex;int lock;int unlock;
public:JzLockfreeList() :mutex(0), lock(0), unlock(1) {};~JzLockfreeList() {};void Lock(){while (!__sync_bool_compare_and_swap(&mutex, lock, 1)){usleep(100);}}void Unlock(){__sync_bool_compare_and_swap(&mutex, unlock, 0);}void Push(T data){Lock();list.push_back(data);Unlock();}T Front(){Lock();T data = list.front();Unlock();return data;}void PopFront(){Lock();list.pop_front();Unlock();}bool IsEmpty(){Lock();if (list.empty()){Unlock();return true;}else{Unlock();return false;}}bool Find(T data){typename std::list<T>::iterator it;Lock();for (it = list.begin(); it != list.end(); ++it){if (*it == data){Unlock();return true;}}Unlock();return false;}
};JzLockfreeList<int> LF;thread_local int num = 1;
//生产者
void Producer()
{while(true){num++;cout<<"num push:"<<num<<endl;LF.Push(num);sleep(2);}
}//消费者
void Customer()
{while(true){if (!LF.IsEmpty()){cout <<"num get " <<LF.Front() <<endl;LF.PopFront();}sleep(1);}
}int main()
{thread t1(Producer);thread t2(Customer);thread t3(Customer);t1.join();t2.join();t3.join();return 0;
}

在C++11 中出现了CAS的用法,也为我们提供了API;

/*
* @brief:compare & swap(CAS)。如果等于expect则swap,否则就返回--是否交换成功, 注意expect如果不相等,会把当前值写入到expected里面。
* 相比于strong,weak可能会出现[spurious wakeup](<http://en.wikipedia.org/wiki/Spurious_wakeup>).
* @param          若x等于expect,则设置为desired 返回true,
*                 否则最新值写入expect,返回false
*/
class atomic {
bool compare_exchange_strong(T& expect /*用来比较的值*/, T desired/*用来设置的值*/)
bool compare_exchange_weak(T& expect, T desired)
}

其实在CAS中,还有一种异常产生,也就是常说的ABA的现象。所谓ABA现象就是当前现象期望值是A,某个线程将A改为B,另外线程将B改为A,导致当前线程误以为还是原来的值,然后操作就会导致一些异常出现。

这里我们可以借用数据库乐观锁的方式,维护一个全局的版本号或者是标志,每次修改的时候需要期望值和内存值相等并且标识也没有发生改变的时候采取更新值。


无锁(CAS)本身编程就不是很友好,如果没有彻底掌握,最好还是使用锁去编写。

ompare_exchange_weak(T& expect, T desired)
}


****其实在CAS中,还有一种异常产生,也就是常说的`ABA`的现象。所谓ABA现象就是当前现象期望值是A,某个线程将A改为B,另外线程将B改为A,导致当前线程误以为还是原来的值,然后操作就会导致一些异常出现。这里我们可以借用数据库乐观锁的方式,维护一个全局的版本号或者是标志,每次修改的时候需要期望值和内存值相等并且标识也没有发生改变的时候采取更新值。****无锁(CAS)本身编程就不是很友好,如果没有彻底掌握,最好还是使用锁去编写。CAS 更多的是一种思想,也是实现高性能编程的一种途径,目前已经有一些开源级别的无锁库可以提供我们使用,也许这些才是我们最好的选择。

这篇关于一文带你彻底理解高性能无锁队列的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/923570

相关文章

一文详解SpringBoot响应压缩功能的配置与优化

《一文详解SpringBoot响应压缩功能的配置与优化》SpringBoot的响应压缩功能基于智能协商机制,需同时满足很多条件,本文主要为大家详细介绍了SpringBoot响应压缩功能的配置与优化,需... 目录一、核心工作机制1.1 自动协商触发条件1.2 压缩处理流程二、配置方案详解2.1 基础YAML

一文详解如何从零构建Spring Boot Starter并实现整合

《一文详解如何从零构建SpringBootStarter并实现整合》SpringBoot是一个开源的Java基础框架,用于创建独立、生产级的基于Spring框架的应用程序,:本文主要介绍如何从... 目录一、Spring Boot Starter的核心价值二、Starter项目创建全流程2.1 项目初始化(

一文带你了解SpringBoot中启动参数的各种用法

《一文带你了解SpringBoot中启动参数的各种用法》在使用SpringBoot开发应用时,我们通常需要根据不同的环境或特定需求调整启动参数,那么,SpringBoot提供了哪些方式来配置这些启动参... 目录一、启动参数的常见传递方式二、通过命令行参数传递启动参数三、使用 application.pro

一文带你深入了解Python中的GeneratorExit异常处理

《一文带你深入了解Python中的GeneratorExit异常处理》GeneratorExit是Python内置的异常,当生成器或协程被强制关闭时,Python解释器会向其发送这个异常,下面我们来看... 目录GeneratorExit:协程世界的死亡通知书什么是GeneratorExit实际中的问题案例

一文详解SQL Server如何跟踪自动统计信息更新

《一文详解SQLServer如何跟踪自动统计信息更新》SQLServer数据库中,我们都清楚统计信息对于优化器来说非常重要,所以本文就来和大家简单聊一聊SQLServer如何跟踪自动统计信息更新吧... SQL Server数据库中,我们都清楚统计信息对于优化器来说非常重要。一般情况下,我们会开启"自动更新

一文详解kafka开启kerberos认证的完整步骤

《一文详解kafka开启kerberos认证的完整步骤》这篇文章主要为大家详细介绍了kafka开启kerberos认证的完整步骤,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、kerberos安装部署二、准备机器三、Kerberos Server 安装1、配置krb5.con

一文详解Nginx的强缓存和协商缓存

《一文详解Nginx的强缓存和协商缓存》这篇文章主要为大家详细介绍了Nginx中强缓存和协商缓存的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、强缓存(Strong Cache)1. 定义2. 响应头3. Nginx 配置示例4. 行为5. 适用场景二、协商缓存(协

Spring Boot整合消息队列RabbitMQ的实现示例

《SpringBoot整合消息队列RabbitMQ的实现示例》本文主要介绍了SpringBoot整合消息队列RabbitMQ的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目录RabbitMQ 简介与安装1. RabbitMQ 简介2. RabbitMQ 安装Spring

一文详解如何在Python中使用Requests库

《一文详解如何在Python中使用Requests库》:本文主要介绍如何在Python中使用Requests库的相关资料,Requests库是Python中常用的第三方库,用于简化HTTP请求的发... 目录前言1. 安装Requests库2. 发起GET请求3. 发送带有查询参数的GET请求4. 发起PO

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque