百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

CAS原子操作实现无锁及性能分析 cas无锁算法

liebian365 2024-10-30 04:48 29 浏览 0 评论

Author:Echo Chen(陈斌)

Email:chenb19870707@gmail.com

Blog:Blog.csdn.net/chen19870707

Date:Nov 13th, 2014

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

1.什么是CAS原子操作

在研究无锁之前,我们需要首先了解一下CAS原子操作——Compare & Set,或是 Compare & Swap,现在几乎所有的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: }

【文章福利】:小编整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!~点击加入832218493(需要自取)


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原子操作实现无锁的性能分析


3.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次,并统计平均值,其结果如下:(单位微秒)

由此可见,无锁操作在性能上远远优于加锁操作,消耗时间仅为加锁操作的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: }

68:

6.参考

1.http://blog.csdn.net/hzhsan/article/details/25837189

2.http://coolshell.cn/articles/8239.html

-

Echo Chen:Blog.csdn.net/chen19870707

————————————————

版权声明:本文为CSDN博主「chen19870707」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/chen19870707/article/details/41083183

相关推荐

go语言也可以做gui,go-fltk让你做出c++级别的桌面应用

大家都知道go语言生态并没有什么好的gui开发框架,“能用”的一个手就能数的清,好用的就更是少之又少。今天为大家推荐一个go的gui库go-fltk。它是通过cgo调用了c++的fltk库,性能非常高...

旧电脑的首选系统:TinyCore!体积小+精简+速度极快,你敢安装吗

这几天老毛桃整理了几个微型Linux发行版,准备分享给大家。要知道可供我们日常使用的Linux发行版有很多,但其中的一些发行版经常会被大家忽视。其实这些微型Linux发行版是一种非常强大的创新:在一台...

codeblocks和VS2019下的fltk使用中文

在fltk中用中文有点问题。英文是这样。中文就成这个样子了。我查了查资料,说用UTF-8编码就行了。edit->Fileencoding->UTF-8然后保存文件。看下下边的编码指示确...

FLTK(Fast Light Toolkit)一个轻量级的跨平台Python GUI库

FLTK(FastLightToolkit)是一个轻量级的跨平台GUI库,特别适用于开发需要快速、高效且简单界面的应用程序。本文将介绍Python中的FLTK库,包括其特性、应用场景以及如何通过代...

中科院开源 RISC-V 处理器“香山”流片,已成功运行 Linux

IT之家1月29日消息,去年6月份,中科院大学教授、中科院计算所研究员包云岗,发布了开源高性能RISC-V处理器核心——香山。近日,包云岗在社交平台晒出图片,香山芯片已流片,回片后...

Linux 5.13内核有望合并对苹果M1处理器支持的初步代码

预计Linux5.13将初步支持苹果SiliconM1处理器,不过完整的支持工作可能还需要几年时间才能完全完成。虽然Linux已经可以在苹果SiliconM1上运行,但这需要通过一系列的补丁才能...

Ubuntu系统下COM口测试教程(ubuntu port)

1、在待测试的板上下载minicom,下载minicom有两种方法:方法一:在Ubuntu软件中心里面搜索下载方法二:按“Ctrl+Alt+T”打开终端,打开终端后输入“sudosu”回车;在下...

湖北嵌入式软件工程师培训怎么选,让自己脱颖而出

很多年轻人毕业即失业、面试总是不如意、薪酬不满意、在家躺平。“就业难”该如何应对,参加培训是否能改变自己的职业走向,在湖北,有哪些嵌入式软件工程师培训怎么选值得推荐?粤嵌科技在嵌入式培训领域有十几年经...

新阁上位机开发---10年工程师的Modbus总结

前言我算了一下,今年是我跟Modbus相识的第10年,从最开始的简单应用到协议了解,从协议开发到协议讲解,这个陪伴了10年的协议,它一直没变,变的只是我对它的理解和认识。我一直认为Modbus协议的存...

创建你的第一个可运行的嵌入式Linux系统-5

@ZHangZMo在MicrochipBuildroot中配置QT5选择Graphic配置文件增加QT5的配置修改根文件系统支持QT5修改output/target/etc/profile配置文件...

如何在Linux下给zigbee CC2530实现上位机

0、前言网友提问如下:粉丝提问项目框架汇总下这个网友的问题,其实就是实现一个网关程序,内容分为几块:下位机,通过串口与上位机相连;下位机要能够接收上位机下发的命令,并解析这些命令;下位机能够根据这些命...

Python实现串口助手 - 03串口功能实现

 串口调试助手是最核心的当然是串口数据收发与显示的功能,pzh-py-com借助的是pySerial库实现串口收发功能,今天痞子衡为大家介绍pySerial是如何在pzh-py-com发挥功能的。一、...

为什么选择UART(串口)作为调试接口,而不是I2C、SPI等其他接口

UART(通用异步收发传输器)通常被选作调试接口有以下几个原因:简单性:协议简单:UART的协议非常简单,只需设置波特率、数据位、停止位和校验位就可以进行通信。相比之下,I2C和SPI需要处理更多的通...

同一个类,不同代码,Qt 串口类QSerialPort 与各种外设通讯处理

串口通讯在各种外设通讯中是常见接口,因为各种嵌入式CPU中串口标配,工业控制中如果不够还通过各种串口芯片进行扩展。比如spi接口的W25Q128FV.对于软件而言,因为驱动接口固定,软件也相对好写,因...

嵌入式linux为什么可以通过PC上的串口去执行命令?

1、uboot(负责初始化基本硬bai件,如串口,网卡,usb口等,然du后引导系统zhi运行)2、linux系统(真正的操作系统)3、你的应用程序(基于操作系统的软件应用)当你开发板上电时,u...

取消回复欢迎 发表评论: