一步一步写线程之十二无锁编程

一、无锁编程

无锁编程并不是真正的无锁,只是在软件上消除了锁(或者说消除了传统认知中的锁)。牺牲CPU的占用时间来换取效率。无论是传统的单线程编程还是后来的多线程编程及至并发编程,其实抽象出来的模型就是生产者和消费者。这种生产者和消费者的模型,在很多情况下其实是不需要锁也不需要缓冲队列之类的,各自干各自的活就可以。但事情总不都是按照想象进行的,总有一些生产者和消费者的任务是不匹配的,在这种情况下,如何更好的从算法层次上将生产者和消费者的匹配度做的更好,就是一个重要的问题。
体现到现实世界来,就是提高工作效率,降低工作成本。生产者和消费者有1:1,1:N,还有N:1和N:N几种关系。在生产者和消费者能力不对等的情况下,就需要一个缓冲区(一般是以队列来实现)来存储工作任务,这时,任务的生产和消费端都需要进行锁的控制,这是前面学习的一个重要的知识点。
但是,有没有一种办法,可以让任务向队列缓冲区插入和读取时,不使用锁呢?答案就是使用无锁编程。

二、无锁编程的要点

1、原子编程
原子编程其实就是原子操作的行为,这对于大家可能已经很熟悉了,会用不会用放一边,肯定耳朵都听得长茧子了。原子操作非常容易理解,就是原子不可分割,那么这个操作也是必须一气呵成,不能被多线程或者其它类似的任务打断。那么这就保证了数据的完整性和一致性。在c++特别是c++11后的新标准中,提供了大量的原子操作的类型如std::atomic。
2、内存序
内存序其实是一种指令操作的约定或者说标准,用来匹配硬件与上层软件为达到处理顺序维持一致性的前提。而在多线程和分布式编程中,经常会遇到这种情况,特别是不同的架构CPU及不同的操作系统中,这种更是经常遇到的。
其实无锁队列本身就是一种特殊的生产者和消费者,而无锁队列的实现,对整体行为操作的原子性和顺序性提出了严格的要求。一般来说,实现这种机制的最基础的方法是使用内存屏障。内存屏障又可以分为内存屏障和编译器屏障,听名字就可以知道,它们主要是用来防止编译器或处理器进行指令重排,保持多线程环境下的数据一致性。内存屏障其实是一种计算机上的抽象的概念,具体的不同的平台和语言都有自己的实现机制,在c++中就各种的锁和原子操作等。

3、CAS的ABA问题
CAS,即Compare-And-Swap,比较和交换,其实就是大家认知里的无锁编程的基础技术。它采用了一种原子操作加CPU循环等待的方式来实现安全的数据读写。在c++中,提供了几个重要的CAS的接口函数,如compare_exchange_strong和compare_exchange_weak等。
可能许多开发者听到无锁编程会眼前一亮,心想总算扔掉了锁这个包袱,且先不要乐观。基于CAS的无锁编程,确实有不少优点,但这里面缺点也不少。这个在前面分析过。这里面有一个很让人难受的问题,那就是ABA问题。什么是ABA问题呢?举个例子就明白了,有一个共享变量的值是10,线程1修改其为11,然后线程2修改其为12,随后又修改其为11,此时线程1再操作此变量时,发现其没有改变。则其随后针对的处理行为可能就会出现问题。对,换句话说,ABA问题,只有在场景中需要处理时才有意义,否则可以忽略。而这种需要处理的场景,往往是涉及到资产的情况,那么这就非常重要了。
如何解决ABA问题呢?一般来说,是使用版本号或者打时间戳等方式来解决。ABA问题的本质就是放弃使用锁导致的线程自由处理共享变量付出的代价。

4、优化处理
没有任何一种手段是普适的。无锁队列也是如此,所以没有最好,只有最合适即针对实际的场景进行优化。无锁队列的优化非常复杂,它不但涉及到传统的优化问题,如一些指令并发、OS的API控制等等,还要处理对循环等待的时间,次数是否处理失败的情况以及减少误操作的机会等等。特别是在分布式编程中,更是复杂,这就需要系统的掌握相关的底层知识和开发技术等。

5、无锁编程的API
说到无锁编程,其实这个在各个平台都有自己提供的接口。在c++编程中提供了:

bool compare_exchange_weak( T& expected, T desired,std::memory_order success,std::memory_order failure ) noexcept;
bool compare_exchange_weak( T& expected, T desired,std::memory_order success,std::memory_order failure ) volatile noexcept;
bool compare_exchange_weak( T& expected, T desired,std::memory_order order =std::memory_order_seq_cst ) noexcept;
bool compare_exchange_weak( T& expected, T desired,std::memory_order order =std::memory_order_seq_cst ) volatile noexcept;
bool compare_exchange_strong( T& expected, T desired,std::memory_order success,std::memory_order failure ) noexcept;
bool compare_exchange_strong( T& expected, T desired,std::memory_order success,std::memory_order failure ) volatile noexcept;
bool compare_exchange_strong( T& expected, T desired,std::memory_order order =std::memory_order_seq_cst ) noexcept;
bool compare_exchange_strong( T& expected, T desired,std::memory_order order =std::memory_order_seq_cst ) volatile noexcept;

强弱二者的不同在于针对不同的架构处理器,weak允许出现偶然的错误返回。这样做的目的只有一个,在某些情况下可能效率会更高。
而LINUX GNUC标准中提供了:

bool __atomic_compare_exchange_n(type *ptr,              // 比较的ptrtype *expected,         // 旧值,返回ptr指向的值type desired,           // 设置的新值bool weak,              // 强一致或弱一致int success_memorder,   // 成功时内存序int failure_memorder    // 失败时内存序
)

在Windows中标准提供了:

//32位
LONG InterlockedCompareExchange([in, out] LONG volatile *Destination,//指向目标值的指针[in]      LONG          ExChange,//交换值。[in]      LONG          Comperand//要与 Destination 进行比较的值
);//函数将 Destination 值与 Compareand 值进行比较。 如果 Destination 值等于 Compareand 值, 则 Exchange 值将存储在 Destination 指定的地址中。 否则,不会执行任何操作。
//64位
LONG64 InterlockedCompareExchange64([in, out] LONG64 volatile *Destination,[in]      LONG64          ExChange,[in]      LONG64          Comperand
);
//指针
PVOID InterlockedCompareExchangePointer([in, out] PVOID volatile *Destination,[in]      PVOID          Exchange,[in]      PVOID          Comperand
);

Windows的相关设置比较简单而且其文档也比较全,这也是微软提供的文档相对丰富原因。

三、无锁编程的应用场景

无锁编程听起来比有锁编程要好很多啊,肯定要包打天下啊。可事实并不是,换句话说,无锁编程也是有其的应用场景的。首先需要了解一下有锁编程缺点:
1、线程切换引起的Cache失效
2、阻塞引起的线程切换和线程休眠
3、内存分配时的锁导致的性能下降
而无锁编程就是有针对性的通过CPU忙等待(牺牲CPU时间)而不是休眠线程来换取1和2的缓解的。所以无锁编程适应场景也就出来了:
1、读写操作频繁,一般建议在十万量级以上(忙等时间尽量小),至少也得在万级才可以考虑
2、读写操作耗时尽量短或者说任务无阻塞操作(忙等时间尽量小)
而对于3,无锁编程其实和有锁编程解决的方式是类似的。或者直接分配好的数组或者使用内存池。
从上面可以再次印证一个事实,只有最合适,没有最优。一切都是平衡的结果。

四、无锁编程的实现机制和实际库应用

无锁编程的实现其实主要有两种,一种是基于链表的实现,这种在资料中非常容易找到。通常是一个链表来模拟实现无锁的读写;另外一个就是使用数组。当然,既然二者都可以实现,那么混合着也可以实现。这个看开发者个人的喜好的实际的情况。
而在实际应用中,包括许多技术大牛,研究论文和有名的框架都对无锁编程进行了阐述和分析,并给出了相关的实现代码。比如:《Implementing Lock-Free Queues》和《Simple, Fast, and Practical Non-Blocking and Blocking ConcurrentQueue Algorithms》等。而框架实现中常见的有intel tbb,folly和boost。比如boost中的lockfree::queue 和lockfree::stack等。而folloy中则提供了AtomicIntrusiveLinkedList等。至于tbb,无锁搞得还是相当好,有兴趣自己下载源码分析即可。
说明:网上有太多的无锁编程的例子,大家要仔细分析,去芜存菁,不要乱了阵脚。

五、简单的实现

先看一下简单的CAS的编程:

#include <atomic>template<typename T>
struct node
{T data;node* next;node(const T& data) : data(data), next(nullptr) {}
};template<typename T>
class stack
{std::atomic<node<T>*> head;
public:void push(const T& data){node<T>* new_node = new node<T>(data);// put the current value of head into new_node->nextnew_node->next = head.load(std::memory_order_relaxed);// now make new_node the new head, but if the head// is no longer what's stored in new_node->next// (some other thread must have inserted a node just now)// then put that new head into new_node->next and try againwhile (!head.compare_exchange_weak(new_node->next, new_node,std::memory_order_release,std::memory_order_relaxed)); // the body of the loop is empty// Note: the above use is not thread-safe in at least
// GCC prior to 4.8.3 (bug 60272), clang prior to 2014-05-05 (bug 18899)
// MSVC prior to 2014-03-17 (bug 819819). The following is a workaround:
//      node<T>* old_head = head.load(std::memory_order_relaxed);
//      do
//      {
//          new_node->next = old_head;
//      }
//      while (!head.compare_exchange_weak(old_head, new_node,
//                                         std::memory_order_release,
//                                         std::memory_order_relaxed));}
};int main()
{stack<int> s;s.push(1);s.push(2);s.push(3);
}

再看一个 strong CAS:

#include <atomic>
#include <iostream>std::atomic<int> ai;int tst_val = 4;
int new_val = 5;
bool exchanged = false;void valsout()
{std::cout << "ai = " << ai<< "  tst_val = " << tst_val<< "  new_val = " << new_val<< "  exchanged = " << std::boolalpha << exchanged<< '\n';
}int main()
{ai = 3;valsout();// tst_val != ai   ==>  tst_val is modifiedexchanged = ai.compare_exchange_strong(tst_val, new_val);valsout();// tst_val == ai   ==>  ai is modifiedexchanged = ai.compare_exchange_strong(tst_val, new_val);valsout();
}

无锁编程的实现其实并没有想象中的难,但之所以大家感觉到有些难的原因不外乎两个:一个是应用的场景对大多数程序员来说不存在,也就是用得少;第二个就是无锁编程与数据结构、算法和OS甚至编译等基础知识都有较强的相关性,即使想用好可能也需要补充很多相关的技术。
这里只是把无锁编程进行一个简单的例程说明,无锁队列的实现,在后期再完善。

六、总结

这里需要声明一个问题,就是在生产者消费者绝对不对等的情况下,使用何种算法和技巧都是没有办法解决问题的。这句话是什么意思呢?就是说十个人干得活,如果只有一个小孩来干,用什么方法在当前状态下也是无解的。但如果合理的采用一些调度算法,安排一下流程,可能五个人就可以完成十个人的工作量。这是不是有点象武学上的“一力降十会”?
同样,无锁队列的目的是提高效率,而不是从解决不可能解决的问题。大家千万不要走进误区,切记!

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

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

相关文章

基于移动多媒体信源与信道编码调研

前言 移动多媒体是指在移动通信环境下&#xff0c;通过无线网络传输的音频、视频、图像等多种媒体信息。移动多媒体的特点是数据量大、传输速率高、服务质量要求高&#xff0c;因此对信源编码和信道编码的性能提出了更高的要求。 本文对进3年的移动多媒体信源与信道编码的研究…

数美滑块研究

周一&#xff0c;在清晨的阳光照耀下&#xff0c;逆向山脚下的小镇宁静而安详。居民们忙碌地开始一天的生活&#xff0c;而在爬虫镇子的边缘&#xff0c;一座古朴的道观显得格外神秘。 阿羊正静静地坐在青石长凳上&#xff0c;摸鱼养神。突然&#xff0c;一道清脆的声音在他耳…

【C++】AVL树和红黑树模拟实现

AVL树和红黑树 1. 背景2. AVL树的概念3. AVL树节点的定义4. AVL树的插入5. AVL树的旋转5.1. 左单旋5.2. 右单旋5.3. 左右单旋5.4. 右左单旋5.5. 旋转总结 6. AVL树的验证7. AVL树的性能8. 红黑树的概念9. 红黑树的节点的定义10. 红黑树的插入10.1. 情况一10.2.情况二 11. 红黑树…

【建议收藏】30个较难Python脚本,纯干货分享

本篇较难&#xff0c;建议优先学习上篇 &#xff1b;20个硬核Python脚本-CSDN博客 接上篇文章&#xff0c;对于Pyhon的学习&#xff0c;上篇学习的结束相信大家对于Pyhon有了一定的理解和经验&#xff0c;学习完上篇文章之后再研究研究剩下的30个脚本你将会有所成就&…

Transformer详解(2)-位置编码

位置编码公式 偶数位置用sin,奇数位置用cos. d_model 表示token的维度&#xff1b;pos表示token在序列中的位置&#xff1b;i表示每个token编码的第i个位置&#xff0c;属于[0,d_model)。 torch实现 import math import torch from torch import nn from torch.autograd im…

pycharm配置python开发环境—miniconda+black+gitlab

下载miniconda管理python开发环境 miniconda下载地址&#xff1a;https://docs.anaconda.com/free/miniconda/ miniconda最新版本的python版本是python3.12.2&#xff0c;下载这个miniconda最新版本后&#xff0c;会导致执行conda create -n py31013 python3.10.13指令配置py…

如何设计电商 SaaS 系统中的免费服务和增值服务

随着电子商务的迅猛发展&#xff0c;越来越多的企业选择使用 SaaS&#xff08;Software as a Service&#xff09;平台来搭建自己的电商系统。为了吸引更多用户&#xff0c;电商 SaaS 系统通常会提供免费服务和增值服务。如何合理地设计这些服务&#xff0c;既能吸引新用户&…

使用HTTP长连接减少文件描述符和端口占用

在当今互联网技术飞速发展的背景下&#xff0c;高并发处理能力已经成为衡量服务器性能的一个重要标准。面对高并发场景&#xff0c;服务器需要同时应对大量的请求&#xff0c;这就带来了一个棘手的问题&#xff1a;资源有限。具体来说&#xff0c;文件描述符和端口号&#xff0…

ES实例演示一

温馨提示&#xff1a;本文所有API操作都是基于Elasticsearch 7.17 .8版本 1、文档的基本 CRUD 与批量操作 ############Create Document############ #create document. 自动生成 _id POST users/_doc {"user" : "Mike","post_date" : "20…

「公 告」根据中华人民共和国法律,Bing 在中国内地暂停 “搜索自动建议” 功能 30 天。

根据中华人民共和国法律&#xff0c;Bing 中国已经被政府有关部门要求在中国内地暂停 “搜索自动建议” 功能 30 天。作为全球性搜索平台&#xff0c;Bing 将持续致力于尊重法治与用户获取信息的权利&#xff0c;在遵守法律的前提下最大限度地帮助客户寻找所需信息。 Bing Chin…

数据结构---优先级队列(堆)

博主主页: 码农派大星. 数据结构专栏:Java数据结构 关注博主带你了解更多数据结构知识 1. 优先级队列 1.1 概念 前面介绍过队列&#xff0c;队列是一种先进先出(FIFO)的数据结构&#xff0c;但有些情况下&#xff0c;操作的数据可能带有优先级&#xff0c;一般出队 列时&am…

微软Copilot+ PC:Phi-Silica

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调重新阅读。而最新科技&#xff08;Mamba&#xff0c;xLSTM,KAN&#xff09;则提供了大模…

Tkinter描述

Tkinter是Python中的一个标准GUI库&#xff0c;使用Tcl/Tk作为底层实现&#xff0c;提供了创建图形用户界面的工具。Tkinter提供了一组标准的GUI元素和布局管理器&#xff0c;帮助开发人员快速构建应用程序。使用Tkinter&#xff0c;可以快速创建简单的GUI应用程序&#xff0c;…

C++入门:从C语言到C++的过渡(2)

目录 1.缺省参数 1.1缺省参数的概念 1.2缺省参数的分类及使用 1.3注意点 2.函数重载 2.1函数重载的定义 2.2函数重载的情况分类 2.3注意 2.4函数名修饰规则 3.引用 3.1引用的概念 3.2注意事项 3.3常引用 4.4引用的使用场景 4.4.1作为函数的参数 4.4.2做函数返回…

【学习笔记】Windows GDI绘图目录

题外话 不知几时开始&#xff0c;觉得学习过程中将内容记录下来&#xff0c;有助于加强记忆&#xff0c;还方便后续查找&#xff0c;顺便帮助有需要的人&#xff0c;更有来自您阅读、点赞、收藏和评论时给我带来的动力与兴奋。 目录 【学习笔记】Windows GDI绘图(一)图形概述…

B站大数据分享视频创作300天100+原创内容4000+粉

以今年五一作为一个里程碑参考点&#xff0c;给明年一个可以比较的数据。 我正经发力创作是2023.06.17 (前面几个视频是试水)&#xff0c;300天不到一年时间 创作了100原创数据相关视频&#xff0c;创作频率应该很高了&#xff0c;收获了下面几个数字&#xff0c;审视自身&…

如何建设高效的外贸自建站?

建设高效的外贸自建站&#xff0c;首先要从明确目标和受众开始。了解你的目标市场和潜在客户是关键&#xff0c;这样你可以有针对性地进行设计和内容创作。站点的设计应该简洁明了&#xff0c;导航要方便&#xff0c;确保访客户能够快速找到所需的信息。 而内容是网站的核心。…

Java基础的语法---String

Java的String类是不可变的&#xff0c;意味着一旦创建&#xff0c;其值就不能被改变。String类提供了丰富的API来操作字符串。 以下是一些常用的方法&#xff1a; 构造方法&#xff1a; 有以下几种常见的&#xff1a; public class stringlearn {public static void main(S…

LNWT--篇章二小测

问题1: Transformer中的softmax计算为什么需要除以$d_k$? 为了稳定数值计算和防止梯度消失或爆炸问题 问题2: Transformer中attention score计算时候如何mask掉padding位置&#xff1f; 将掩码矩阵添加到缩放后的注意力分数上。由于我们使用了负无穷&#xff0c;经过softma…

ATmega328P加硬件看门狗MAX824L看门狗

void Reversewdt(){ //硬件喂狗&#xff0c;11PIN接MAX824L芯片WDIif (digitalRead(11) HIGH) {digitalWrite(11, LOW); //低电平} else {digitalWrite(11, HIGH); //高电平 }loop增加喂狗调用 void loop() { …… Reversewdt();//喂狗 }