并行编程中的lock free技术

并行编程中的lock free技术lockfree(中文一般叫“无锁”,一般指的都是基于CAS指令的无锁技术)是利用处理器的一些特殊的原子指令来避免传统并行设计中对锁(lock)的使用。众所周知,锁在解决并行过程中资源访问问题的同时可能会引入诸多新的问题,比如死锁(deadlock),另外锁的申请/释放对性能也有不小的影响,当然最大的问题还在于使用锁的代码模块通常难以进行组合。lockfree的目标就是要消除锁对编程

大家好,又见面了,我是你们的朋友全栈君。

lock free (中文一般叫“无锁”,一般指的都是基于CAS指令的无锁技术) 是利用处理器的一些特殊的原子指令来避免传统并行设计中对锁(lock)的使用。

众所周知,锁在解决并行过程中资源访问问题的同时可能会引入诸多新的问题,比如死锁(dead lock),另外锁的申请/释放对性能也有不小的影响,当然最大的问题还在于使用锁的代码模块通常难以进行组合。lock free的目标就是要消除锁对编程带来的不利影响。

C++大牛Andrei Alexandrescu (就是把template玩得炉火纯青的那个gg,《MODERN C++ DESIGN》的作者)的文章《Lock-Free Data Structures》是lock free方面的代表作,有兴趣的最好看一下,就当开阔思路吧。(btw, Andrei这哥们儿好像就是爱玩点bt的东西,呵呵)。

不过lock free本身也是目前各种并行解决方案中比较受争议的一种: 一来这项技术有点过于诡异,掌握起来颇有难度,不过另一方面,因为它是完全基于最基本的编程技术,所以并不依赖任何语言/平台,理论上应用面可以很广。

在并行编程方面,函数式的那些东西(比如ErlangHaskell之类的)算得上是另起炉灶,而lock free算得上是就地解决吧。所以各种方案其实也不矛盾,都是为人民服务嘛;)

个人对lock free的观点是这项技术不应该也不会大面积地应用在实际编程中,毕竟像这种高难度的东西还是有点曲高和寡。对于技术本身反正是见仁见智,爱用就用,不用拉倒呗。不过我想无论是否在实际当中使用lock free技术,了解和研究这项技术本身都会对理解并行编程有很大的帮助。

lock free的基础是CAS (Compare And Swap)函数,它的功能可以用如下的代码描述:

[cpp] 
view plain  
copy

  1. template <class T>  
  2. bool CAS(T* addr, T expected, T value) {  
  3.    if (*addr == expected) {  
  4.       *addr = value;  
  5.       return true;  
  6.    }  
  7.    return false;  
  8. }   

如果以前没有真正了解过lock free技术,可能会产生疑惑,这个函数对解决我们并行中的竞争问题能有什么帮助呢?如果你有这样的疑问,没问题,因为我第一次见到这个的时候也是一头雾水。不过实际上这个函数只是描述了Compare And Swap的执行过程,函数本身并不能直接被使用,只是伪代码描述而已,切记。不过现代处理器通常都实现了对应CAS功能的原子指令,比如x86汇编里面的“ CMPXCHG ”就提供了这样的功能,所以CAS的实现实际是平台相关的。文章里面给出了Linux下利用asm的实现,相较之下windows上面的实现可以简单一些,因为windows提供了一族以InterlockedCompareExchange开头的API来封装具体的实现,比如其中的InterlockedCompareExchange函数声明如下:

LONG

InterlockedCompareExchange(

    IN OUT PLONG  Destination,

    IN LONG  Exchange,

    IN LONG  Comparand

);

这里值得注意的是函数的返回值是原始的*Destination内容,并不是像上面的C++代码描述的那样会直接返回一个布尔值指示交换操作是否真正发生。所以返回值的工作必须由我们自己来完成。显然这里不能在函数开始处对*Destination和Comparand进行比较然后用if/else这样的分支来选择返回true还是false,因为那样的话就必须有一个lock来进行保护了。我们好不容易找到个办法来避免对lock的依赖,岂能又给绕回去了?这里的标准做法是用API调用的返回值与Comparand传入参数进行比较,因为API确保返回的是一个LONG类型的值对象,这个值始终都是存在于函数的栈上面,所以即便在比较之前发生中断并且实际的*Destination内容又被其它线程修改了,也并不影响此处的比较结果,当然也不会影响CAS函数的返回值了。代码实现如下:

[cpp] 
view plain  
copy

  1. template<class T>  
  2. bool CAS(T* addr, T expected, T fresh) {  
  3.    return expected == (T)InterlockedCompareExchange((LONG *)addr, (LONG)fresh, (LONG)expected);  
  4. }  

当然针对实际调用中T通常为指针的情况可以直接用InterlockedCompareExchangePointer来避免显示的类型转换,可以考虑再加一个偏特化的template,不过处理方法一样。

最近发现codeproject上有一篇文章分别用C++和C#实现了lock free的算法,不过很遗憾这个实现是有问题的。由此也可以说明并行程序设计特别是lock free确实不是一件容易的事情,连这样的文章都弄错了。看一下它的CAS函数实现:

[cpp] 
view plain  
copy

  1. bool CAS(pointer_t& dest,pointer_t& compare, pointer_t& value)  
  2. {  
  3.    if(compare.ptr==InterlockedCompareExchangePointer((PVOID volatile*)&dest.ptr,value.ptr,compare.ptr)) {  
  4.       InterlockedExchange(&dest.count,value.count);  
  5.       return true;  
  6.    }  
  7.    return false;  
  8. }  

这里的问题是函数里面用了两条语句来完成对目标对象的修改,虽然两条语句本身都是atomic的,不过在它们中间仍然可能发生中断,所以这个CAS函数并没有发挥预期的作用。实际上基于CAS语句的lock free技术的本质是对于任何数据的修改并不直接修改对象本身,而是先去修改目标对象的一份拷贝(copy),然后通过实现为atomic的一次交换操作将修改后的拷贝内容赋值给目标对象。所以CAS语句通常像下面这样使用:

[cpp] 
view plain  
copy

  1. do {  
  2.    pOldData = pDest;  
  3.    delete pNewData;  
  4.    pNewData = pDest->copy();  
  5.    // modify pNewData  
  6. while (!CAS(&pDest, pOldData, pNewData))  

其实类似的思路也应用到解决其它问题。如果还没想到点什么的话可以去找一份C++里面智能指针(smart pointer)的代码来看看,所以这其实也是异常安全(exception safe)编码的必备武器之一。

上面的copy操作效率比较低,所以牛牛们在具体应用中想出了各种方法来减小数据copy的粒度。不过无论如何,将CAS语句实现成多条需要读写原始dest数据的操作都是不正确的。此处一种可行的做法是使用类似InterlockedCompareExchange64之流的加强版CAS API来一石二鸟,具体实现上还有很多巧妙的解法,已超出本文的范畴,就此打住。

题外话:如果之前对异常安全(exception safety)编码有所了解,可能会发现和本文谈的lock free有相似的地方。因为在异常安全里面对资源的修改最好的方式并不是直接修改目标对象本身,也是先创建/修改一份副本对象,最后通过保证没有异常抛出的swap操作来修改目标对象的内容。不了解的话可以随便找一份智能指针(smart pointer,比如boost里面的shared_ptr)的源代码l瞧瞧里面是不是用到了很多次std::swap().

lock free为的是线程访问的安全,exception safety为的是保证在抛出异常情况下数据的安全,不过解决问题的基本思路却是一样的。


锁无关的(Lock-Free)数据结构——在避免死锁的同时确保线程继续

锁无关的数据结构与Hazard指针——操纵有限的资源

无锁队列的实现

无锁队列的实现-循环数组

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/161710.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 华为模拟器ensp怎么安装_华为游戏模拟器电脑版

    华为模拟器ensp怎么安装_华为游戏模拟器电脑版ENSP(EnterpriseNetworkSimulationPlatform)是一个图形化网络仿真平台,该平台通过对真实网络设备的仿真模拟,帮助广大ICT从业者和客户快速熟悉华为数通系列产品,了解并掌握相关产品的操作和配置、提升对企业ICT网络的规划、建设、运维能力,从而帮助企业构建更高效,更优质的企业ICT网络。操作环境:Win10专业工作站版、VirtualBox-5.2.26-128414、WinPcap_4_1_3、Wireshark-win64-1.12.4文章目录一、详..

    2022年10月10日
  • matlab之BM3D

    matlab之BM3D1.matlab代码2.

  • 四旋翼飞行器图片(4轴飞行器飞行运动中有)

    1、未知区域地形的构建2、农业方面的应用:(1)运用图像识别技术检测果实数量;(2)检测作物是否发生病虫害,因为当作物出现病虫害时都会有相应的表现现状。具体见链接http://www.aiweibang.com/yuedu/153474153.html3、高层建筑物的搭建

  • linux c 报错 warning: large integer implicitly truncated to unsigned type[-Woverflow]

    linux c 报错 warning: large integer implicitly truncated to unsigned type[-Woverflow]警告的原因是:整数溢出整数溢出:当整数达到它所能表述的最大值时,会重新从起点开始#include<stdio.h>intmain(void){ unsigneda=12345678910; printf(“a=%d\n”,a); return0;}该程序输出以后并不是输出a=12345678910而是:上面的代码还不足以说明清楚下面才是重头戏:#include<stdio.h>intmain(void){ inta=2

  • android之SharedPreferences简介[通俗易懂]

    SharedPreferences是Android平台上一个轻量级的存储类,主要是保存一些常用的配置比如窗口状态,一般在Activity中重载窗口状态onSaveInstanceState保存一般使用SharedPreferences完成,它提供了Android平台常规的Long长整型、Int整型、String字符串型的保存.2个activity 之间的数据传递除了可以通过intent来传递,

  • hive 修改字段类型和增加字段表

    hive 修改字段类型和增加字段表Hive表修改字段类型Altertable表名 changecolumn原字段名称 现字段名称 数据类型 新增字段表altertable表名addcolumns(字段名数据类型)  

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号