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

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

相关文章

一文详解Java异常处理你都了解哪些知识

《一文详解Java异常处理你都了解哪些知识》:本文主要介绍Java异常处理的相关资料,包括异常的分类、捕获和处理异常的语法、常见的异常类型以及自定义异常的实现,文中通过代码介绍的非常详细,需要的朋... 目录前言一、什么是异常二、异常的分类2.1 受检异常2.2 非受检异常三、异常处理的语法3.1 try-

Java的栈与队列实现代码解析

《Java的栈与队列实现代码解析》栈是常见的线性数据结构,栈的特点是以先进后出的形式,后进先出,先进后出,分为栈底和栈顶,栈应用于内存的分配,表达式求值,存储临时的数据和方法的调用等,本文给大家介绍J... 目录栈的概念(Stack)栈的实现代码队列(Queue)模拟实现队列(双链表实现)循环队列(循环数组

一文带你搞懂Python中__init__.py到底是什么

《一文带你搞懂Python中__init__.py到底是什么》朋友们,今天我们来聊聊Python里一个低调却至关重要的文件——__init__.py,有些人可能听说过它是“包的标志”,也有人觉得它“没... 目录先搞懂 python 模块(module)Python 包(package)是啥?那么 __in

Redis消息队列实现异步秒杀功能

《Redis消息队列实现异步秒杀功能》在高并发场景下,为了提高秒杀业务的性能,可将部分工作交给Redis处理,并通过异步方式执行,Redis提供了多种数据结构来实现消息队列,总结三种,本文详细介绍Re... 目录1 Redis消息队列1.1 List 结构1.2 Pub/Sub 模式1.3 Stream 结

一文详解如何在Python中从字符串中提取部分内容

《一文详解如何在Python中从字符串中提取部分内容》:本文主要介绍如何在Python中从字符串中提取部分内容的相关资料,包括使用正则表达式、Pyparsing库、AST(抽象语法树)、字符串操作... 目录前言解决方案方法一:使用正则表达式方法二:使用 Pyparsing方法三:使用 AST方法四:使用字

如何将Python彻底卸载的三种方法

《如何将Python彻底卸载的三种方法》通常我们在一些软件的使用上有碰壁,第一反应就是卸载重装,所以有小伙伴就问我Python怎么卸载才能彻底卸载干净,今天这篇文章,小编就来教大家如何彻底卸载Pyth... 目录软件卸载①方法:②方法:③方法:清理相关文件夹软件卸载①方法:首先,在安装python时,下

电脑死机无反应怎么强制重启? 一文读懂方法及注意事项

《电脑死机无反应怎么强制重启?一文读懂方法及注意事项》在日常使用电脑的过程中,我们难免会遇到电脑无法正常启动的情况,本文将详细介绍几种常见的电脑强制开机方法,并探讨在强制开机后应注意的事项,以及如何... 在日常生活和工作中,我们经常会遇到电脑突然无反应的情况,这时候强制重启就成了解决问题的“救命稻草”。那

深入理解Apache Kafka(分布式流处理平台)

《深入理解ApacheKafka(分布式流处理平台)》ApacheKafka作为现代分布式系统中的核心中间件,为构建高吞吐量、低延迟的数据管道提供了强大支持,本文将深入探讨Kafka的核心概念、架构... 目录引言一、Apache Kafka概述1.1 什么是Kafka?1.2 Kafka的核心概念二、Ka

SpringKafka错误处理(重试机制与死信队列)

《SpringKafka错误处理(重试机制与死信队列)》SpringKafka提供了全面的错误处理机制,通过灵活的重试策略和死信队列处理,下面就来介绍一下,具有一定的参考价值,感兴趣的可以了解一下... 目录引言一、Spring Kafka错误处理基础二、配置重试机制三、死信队列实现四、特定异常的处理策略五

一文详解JavaScript中的fetch方法

《一文详解JavaScript中的fetch方法》fetch函数是一个用于在JavaScript中执行HTTP请求的现代API,它提供了一种更简洁、更强大的方式来处理网络请求,:本文主要介绍Jav... 目录前言什么是 fetch 方法基本语法简单的 GET 请求示例代码解释发送 POST 请求示例代码解释