无锁编程实例

无锁编程实例最近在研究nginx的自旋锁的时候,又见到了GCCCAS原子操作,于是决定动手分析下CAS实现的无锁到底性能如何,网上关于CAS实现无锁的文章很多,但少有研究这种无锁的性能提升的文章,这里就以实验结果和我自己的理解逐步展开。1.什么是CAS原子操作在研究无锁之前,我们需要首先了解一下CAS原子操作——Compare&Set,或是Compare&Swap,现在

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

  • 最近在研究nginx的自旋锁的时候,又见到了GCC CAS原子操作,于是决定动手分析下CAS实现的无锁到底性能如何,网上关于CAS实现无锁的文章很多,但少有研究这种无锁的性能提升的文章,这里就以实验结果和我自己的理解逐步展开。

  • 1.什么是CAS原子操作

    在研究无锁之前,我们需要首先了解一下CAS原子操作——Compare & Set,或是 Compare & Swap,现在几乎所image有的CPU指令都支持CAS的原子操作,X86下对应的是 CMPXCHG 汇编指令。

    大家应该还记得操作系统里面关于“原子操作”的概念,一个操作是原子的(atomic),如果这个操作所处的层(layer)的更高层不能发现其内部实现与结构。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。有了这个原子操作这个保证我们就可以实现无锁了。

    CAS原子操作在维基百科中的代码描述如下:

       1: int compare_and_swap(int* reg, int oldval, int newval)
       2: {
         
         
       3:   ATOMIC();
       4:   int old_reg_val = *reg;
       5:   if (old_reg_val == oldval)
       6:      *reg = newval;
       7:   END_ATOMIC();
       8:   return old_reg_val;
       9: }

  • 也就是检查内存*reg里的值是不是oldval,如果是的话,则对其赋值newval。上面的代码总是返回old_reg_value,调用者如果需要知道是否更新成功还需要做进一步判断,为了方便,它可以变种为直接返回是否更新成功,如下:

  •    1: bool compare_and_swap (int *accum, int *dest, int newval)
       2: {
          
          
       3:   if ( *accum == *dest ) {
          
          
       4:       *dest = newval;
       5:       return true;
       6:   }
       7:   return false;
       8: }

    除了CAS还有以下原子操作:

    • Fetch And Add,一般用来对变量做 +1 的原子操作。
         1: << atomic >>
         2: function FetchAndAdd(address location, int inc) {
              
              
         3:     int value := *location
         4:     *location := value + inc
         5:     return value
         6: }

       
      Test-and-set,写值到某个内存位置并传回其旧值。汇编指令BST。
         1: #define LOCKED 1
         2:  
         3: int TestAndSet(int* lockPtr) {
              
              
         4:     int oldValue;
         5:  
         6:     // Start of atomic segment
         7:     // The following statements should be interpreted as pseudocode for
         8:     // illustrative purposes only.
         9:     // Traditional compilation of this code will not guarantee atomicity, the
        10:     // use of shared memory (i.e. not-cached values), protection from compiler
        11:     // optimization, or other required properties.
        12:     oldValue = *lockPtr;
        13:     *lockPtr = LOCKED;
        14:     // End of atomic segment
        15:  
        16:     return oldValue;
        17: }

       
    • Test and Test-and-set,用来实现多核环境下互斥锁,
         1: boolean locked := false // shared lock variable
         2: procedure EnterCritical() {
              
              
         3:   do {
              
              
         4:     while (locked == true) skip // spin until lock seems free
         5:   } while TestAndSet(locked) // actual atomic locking
         6: }

    2.CAS 在各个平台下的实现

     

    2.1 Linux GCC 支持的 CAS

    GCC4.1+版本中支持CAS的原子操作(完整的原子操作可参看 GCC Atomic Builtins

       1: bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)
       2: type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)

    2.2  Windows支持的CAS

    在Windows下,你可以使用下面的Windows API来完成CAS:(完整的Windows原子操作可参看MSDN的InterLocked Functions

  •    1: InterlockedCompareExchange ( __inout LONG volatile *Target,
       2:                                 __in LONG Exchange,
       3:                                 __in LONG Comperand);

    2.3  C++ 11支持的CAS

    C++11中的STL中的atomic类的函数可以让你跨平台。(完整的C++11的原子操作可参看 Atomic Operation Library

       1: template< class T >
       2: bool atomic_compare_exchange_weak( std::atomic<T>* obj,
       3:                                    T* expected, T desired );
       4: template< class T >
       5: bool atomic_compare_exchange_weak( volatile std::atomic<T>* obj,
       6:                                    T* expected, T desired );

     

  • 3.CAS原子操作实现无锁的性能分析

     

    • image3.1测试方法描述

     
             这里由于只是比较性能,所以采用很简单的方式,创建10个线程并发执行,每个线程中循环对全局变量count进行++操作(i++),循环加2000000次,这必然会涉及到并发互斥操作,在同一台机器上分析 加普通互斥锁、CAS实现的无锁、Fetch And Add实现的无锁消耗的时间,然后进行分析。

     

    3.2 加普通互斥锁代码

       1: #include <stdio.h>
       2: #include <stdlib.h>
       3: #include <pthread.h>
       4: #include <time.h>
       5: #include "timer.h"
       6:  
       7: pthread_mutex_t mutex_lock;
       8: static volatile int count = 0;
       9: void *test_func(void *arg)
      10: {
         
         
      11:         int i = 0;
      12:         for(i = 0; i < 2000000; i++)
      13:         {
         
         
      14:                 pthread_mutex_lock(&mutex_lock);
      15:                 count++;
      16:                 pthread_mutex_unlock(&mutex_lock);
      17:         }
      18:         return NULL;
      19: }
      20:  
      21: int main(int argc, const char *argv[])
      22: {
         
         
      23:     Timer timer; // 为了计时,临时封装的一个类Timer。
      24:     timer.Start();    // 计时开始
      25:     pthread_mutex_init(&mutex_lock, NULL);
      26:     pthread_t thread_ids[10];
      27:     int i = 0;
      28:     for(i = 0; i < sizeof(thread_ids)/sizeof(pthread_t); i++)
      29:     {
         
         
      30:         pthread_create(&thread_ids[i], NULL, test_func, NULL);
      31:     }
      32:  
      33:     for(i = 0; i < sizeof(thread_ids)/sizeof(pthread_t); i++)
      34:     {
         
         
      35:         pthread_join(thread_ids[i], NULL);
      36:     }
      37:  
      38:     timer.Stop();// 计时结束
      39:     timer.Cost_time();// 打印花费时间
      40:     printf("结果:count = %d\n",count);
      41:  
      42:     return 0;
      43: }

    注:Timer类仅作统计时间用,其实现在文章最后给出。

     

    3.2 CAS实现的无锁

     

       1: #include <stdio.h>
       2: #include <stdlib.h>
       3: #include <pthread.h>
       4: #include <unistd.h>
       5: #include <time.h>
       6: #include "timer.h"
       7:  
       8: int mutex = 0;
       9: int lock = 0;
      10: int unlock = 1;
      11:  
      12: static volatile int count = 0;
      13: void *test_func(void *arg)
      14: {
         
         
      15:         int i = 0;
      16:         for(i = 0; i < 2000000; i++)
      17:     {
         
         
      18:         while (!(__sync_bool_compare_and_swap (&mutex,lock, 1) ))usleep(100000);
      19:          count++;
      20:          __sync_bool_compare_and_swap (&mutex, unlock, 0);
      21:         }
      22:         return NULL;
      23: }
      24:  
      25: int main(int argc, const char *argv[])
      26: {
         
         
      27:     Timer timer;
      28:     timer.Start();
      29:     pthread_t thread_ids[10];
      30:     int i = 0;
      31:  
      32:     for(i = 0; i < sizeof(thread_ids)/sizeof(pthread_t); i++)
      33:     {
         
         
      34:             pthread_create(&thread_ids[i], NULL, test_func, NULL);
      35:     }
      36:  
      37:     for(i = 0; i < sizeof(thread_ids)/sizeof(pthread_t); i++)
      38:     {
         
         
      39:             pthread_join(thread_ids[i], NULL);
      40:     }
      41:  
      42:     timer.Stop();
      43:     timer.Cost_time();
      44:     printf("结果:count = %d\n",count);
      45:  
      46:     return 0;
      47: }
      48:  

     

    3.4 Fetch And Add 原子操作

     

       1: #include <stdio.h>
       2: #include <stdlib.h>
       3: #include <pthread.h>
       4: #include <unistd.h>
       5: #include <time.h>
       6: #include "timer.h"
       7:  
       8: static volatile int count = 0;
       9: void *test_func(void *arg)
      10: {
       
       
      11:         int i = 0;
      12:         for(i = 0; i < 2000000; i++)
      13:         {
       
       
      14:             __sync_fetch_and_add(&count, 1);
      15:         }
      16:         return NULL;
      17: }
      18:  
      19: int main(int argc, const char *argv[])
      20: {
       
       
      21:     Timer timer;
      22:     timer.Start();
      23:     pthread_t thread_ids[10];
      24:     int i = 0;
      25:  
      26:     for(i = 0; i < sizeof(thread_ids)/sizeof(pthread_t); i++){
       
       
      27:             pthread_create(&thread_ids[i], NULL, test_func, NULL);
      28:     }
      29:  
      30:     for(i = 0; i < sizeof(thread_ids)/sizeof(pthread_t); i++){
       
       
      31:             pthread_join(thread_ids[i], NULL);
      32:     }
      33:  
      34:     timer.Stop();
      35:     timer.Cost_time();
      36:     printf("结果:count = %d\n",count);
      37:     return 0;
      38: }
      39:  

     

    4 实验结果和分析

    在同一台机器上,各运行以上3份代码10次,并统计平均值,其结果如下:(单位微秒)

    image

    由此可见,无锁操作在性能上远远优于加锁操作,消耗时间仅为加锁操作的1/3左右,无锁编程方式确实能够比传统加锁方式效率高,经上面测试可以发现,可以快到3倍左右。所以在极力推荐在高并发程序中采用无锁编程的方式可以进一步提高程序效率。

    5.时间统计类Timer

    timer.h

       1: #ifndef TIMER_H
       2: #define TIMER_H
       3:  
       4: #include <sys/time.h>
       5: class Timer
       6: {
          
          
       7: public:    
       8:     Timer();
       9:     // 开始计时时间
      10:     void Start();
      11:     // 终止计时时间
      12:     void Stop();
      13:     // 重新设定
      14:     void Reset();
      15:     // 耗时时间
      16:     void Cost_time();
      17: private:
      18:     struct timeval t1;
      19:     struct timeval t2;
      20:     bool b1,b2;
      21: };
      22: #endif

    timer.cpp

       1: #include "timer.h"
       2: #include <stdio.h>
       3:  
       4: Timer::Timer()
       5: {
          
          
       6:     b1 = false;
       7:     b2 = false;
       8: }
       9: void Timer::Start()
      10: {
          
          
      11:     gettimeofday(&t1,NULL);  
      12:     b1 = true;
      13:     b2 = false;
      14: }
      15:  
      16: void Timer::Stop()
      17: {    
      18:     if (b1 == true)
      19:     {
          
          
      20:         gettimeofday(&t2,NULL);  
      21:         b2 = true;
      22:     }
      23: }
      24:  
      25: void Timer::Reset()
      26: {    
      27:     b1 = false;
      28:     b2 = false;
      29: }
      30:  
      31: void Timer::Cost_time()
      32: {
          
          
      33:     if (b1 == false)
      34:     {
          
          
      35:         printf("计时出错,应该先执行Start(),然后执行Stop(),再来执行Cost_time()");
      36:         return ;
      37:     }
      38:     else if (b2 == false)
      39:     {
          
          
      40:         printf("计时出错,应该执行完Stop(),再来执行Cost_time()");
      41:         return ;
      42:     }
      43:     else
      44:     {
          
          
      45:         int usec,sec;
      46:         bool borrow = false;
      47:         if (t2.tv_usec > t1.tv_usec)
      48:         {
          
          
      49:             usec = t2.tv_usec - t1.tv_usec;
      50:         }
      51:         else
      52:         {
          
          
      53:             borrow = true;
      54:             usec = t2.tv_usec+1000000 - t1.tv_usec;
      55:         }
      56:  
      57:         if (borrow)
      58:         {
          
          
      59:             sec = t2.tv_sec-1 - t1.tv_sec;
      60:         }
      61:         else
      62:         {
          
          
      63:             sec = t2.tv_sec - t1.tv_sec;
      64:         }
      65:         printf("花费时间:%d秒 %d微秒\n",sec,usec);
      66:     }
      67: }

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

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

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

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

(0)


相关推荐

  • Enterprise Library 3.1 介绍「建议收藏」

    Enterprise Library 3.1 介绍「建议收藏」EnterpriseLibrary3.1是Microsoftpatterns&practicesEnterpriseLibrary的一个新的发行。EnterpriseLibrary是一个应用程序块的集合。这些可重用的软件组件为帮助开发者解决普通企业开发的挑战而设计。这个EnterpriseLibrary发行包括了二个新的应用程序块,一个用于创建应用程序块和提供程序的软

    2022年10月20日
  • 人人网登录界面[通俗易懂]

    人人网登录界面[通俗易懂]<!DOCTYPEhtml><html> <head><metacharset="UTF-8"><title

  • 关于Java你不知道的那些事之Java注解和反射

    点击上方“全栈程序员社区”,星标公众号 重磅干货,第一时间送达 作者:轻狂书生FS https://blog.csdn.net/LookForDream_/ 前言 我们在实际的工作…

  • C++学习之路——函数重载和运算符重载

    C++学习之路——函数重载和运算符重载一、函数重载C++允许在同一作用域中的某个函数和运算符指定多个定义,分 别称为函数重载和运算符重载重载声明是指一个与之前已经在该作用域内声明过的函数或方法 具有相同名称的声明,但是它们的参数列表和实现不相同。当您调用一个重载函数或重载运算符时,编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策。C++中的函数重…

  • android之AsyncQueryHandler详解

    官方文档对AsyncQueryHandler的解释非常简洁A helper class to help make handling asynchronousContentResolver queries easier下面解释一番,其实明白之后就会发现,真的就是一句话的事情而已.AsyncQueryHandler:异步的查询操作帮助类,其实它同样可以处理增删改,查询其API便可知

  • yui java 右键_给YUI Compressor添加右键命令,完成快捷压缩[通俗易懂]

    yui java 右键_给YUI Compressor添加右键命令,完成快捷压缩[通俗易懂]YUICompressor默认不带右键安装功能YUICompressor非常好用,特别是JS的混淆是众多JSCoding的最爱。可惜官网提供的版本都不具备右键功能,每次压缩都要cmd输入一些命令实在是繁琐,本文就介绍如何给YUICompressor添加右键命令,方便使用。网上已有一些网友写好的安装包,但对应的版本太低,目前最新最多人使用的还是2.4.7版本,而这一版本的带右键功能目前没有在…

发表回复

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

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