并行编程中的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指针——操纵有限的资源

无锁队列的实现

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

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

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • python的tkinter模块的导入_numpy scipy

    python的tkinter模块的导入_numpy scipy在python项目使用cxfreeze进行打包的时候,如果脚本里包括numpy的引用时,在打包时会报importError:cannotimportname’_methods’from’numpy.core’的错误,这时,在打包的setup.py文件中加入整个包numpy的引用即可packages=[“numpy”]options={“build_exe…

    2022年8月30日
    0
  • cmap用法,很详细(转)

    http://hi.baidu.com/wei83523408/blog/item/878ebd3b8898d5e115cecb2b.html一、Map的基本知识  映射(Map),又称为字典(Dictionary),是由关键字(Key)及其对应的元素值(Value)所组成的元素单元(Element)的表单式集合。通常,对于Map而言,使用给定的Key,可以迅速地从

    2022年4月3日
    206
  • 窗宽窗位

    窗宽窗位转自“CT诊断学”中的窗宽窗位部分。窗宽与窗位CT能识别人体内2000个不同灰阶的密度差别。而人的眼睛却只能分辨16个灰阶度。因此,人眼在CT图像上能分辨的CT值应为125Hu(2000/16)。换句话说,人体内不同组织CT值只有相差125Hu以上,才能为人眼所识别。人体软组织CT值多变化在20-50Hu之间,人眼就无法识别。为此,必须进行分段观察,才能使

    2022年6月15日
    64
  • FindWindow使用方法_find do

    FindWindow使用方法_find do查找指定窗口 TCHARszTitle[MAX_PATH]={0}; HWNDhwnd=::FindWindow(TEXT(“#32770”),TEXT(“飞鸽传书IPMessenger”)); if(hwnd!=NULL) { //修改窗口标题 ::SetW…

    2022年8月13日
    3
  • 程序员面试、算法研究、编程艺术、红黑树、机器学习5大系列集锦

    程序员面试、算法研究、编程艺术、红黑树、机器学习5大系列集锦程序员面试、算法研究、编程艺术、红黑树、机器学习5大经典原创系列集锦与总结作者:July–结构之法算法之道blog之博主。时间:2010年10月-2018年5月,一直在不断更新中..出处:http://blog.csdn.net/v_JULY_v。说明:本博客中部分文章经过不断修改、优化,已集结出版成书《编程之法:面试和算法心得》。前言开博4年有余,…

    2022年4月19日
    48
  • java怎么运行_怎样启动JAVA?「建议收藏」

    java怎么运行_怎样启动JAVA?「建议收藏」展开全部在Android中启动Java程序其实有很多种方式,现总结如下一、在Android应用程序中e69da5e887aa62616964757a686964616f31333363373732发送Intent启动Android应用程序这个方式最简单,最常用。在此不在累述。关于Intent的更多内容请阅读《Intent技术简介》二、在shell控制台通过am命令发送Intent来启动Androi…

    2022年7月7日
    39

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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