热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C语言的原子操作

2019独角兽企业重金招聘Python工程师标准###gcc内建函数内建gcc在4.0.1版本后就通过其内建函数支持原子操作。在这之前编程真必须要通过参考各种cpu的指令操作

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

###gcc内建函数 内建gcc在4.0.1版本后就通过其内建函数支持原子操作。在这之前编程真必须要通过参考各种cpu的指令操作手册,用其汇编指令编写原子操作。而gcc通过内建函数屏蔽了这些差异。gcc支持如下原子操作:

#if (GCC_VERSION >= 40100)
/* 内存访问栅 */#define barrier() (__sync_synchronize())
/* 原子获取 */#define AO_GET(ptr) ({ __typeof__(*(ptr)) volatile *_val = (ptr); barrier(); (*_val); })
/*原子设置,如果原值和新值不一样则设置*/#define AO_SET(ptr, value) ((void)__sync_lock_test_and_set((ptr), (value)))
/* 原子交换,如果被设置,则返回旧值,否则返回设置值 */#define AO_SWAP(ptr, value) ((__typeof__(*(ptr)))__sync_lock_test_and_set((ptr), (value)))
/* 原子比较交换,如果当前值等于旧值,则新值被设置,返回旧值,否则返回新值*/#define AO_CAS(ptr, comp, value) ((__typeof__(*(ptr)))__sync_val_compare_and_swap((ptr), (comp), (value)))
/* 原子比较交换,如果当前值等于旧指,则新值被设置,返回真值,否则返回假 */#define AO_CASB(ptr, comp, value) (__sync_bool_compare_and_swap((ptr), (comp), (value)) != 0 ? true : false)
/* 原子清零 */#define AO_CLEAR(ptr) ((void)__sync_lock_release((ptr)))
/* 通过值与旧值进行算术与位操作,返回新值 */#define AO_ADD_F(ptr, value) ((__typeof__(*(ptr)))__sync_add_and_fetch((ptr), (value)))#define AO_SUB_F(ptr, value) ((__typeof__(*(ptr)))__sync_sub_and_fetch((ptr), (value)))#define AO_OR_F(ptr, value) ((__typeof__(*(ptr)))__sync_or_and_fetch((ptr), (value)))#define AO_AND_F(ptr, value) ((__typeof__(*(ptr)))__sync_and_and_fetch((ptr), (value)))#define AO_XOR_F(ptr, value) ((__typeof__(*(ptr)))__sync_xor_and_fetch((ptr), (value)))
/* 通过值与旧值进行算术与位操作,返回旧值 */#define AO_F_ADD(ptr, value) ((__typeof__(*(ptr)))__sync_fetch_and_add((ptr), (value)))#define AO_F_SUB(ptr, value) ((__typeof__(*(ptr)))__sync_fetch_and_sub((ptr), (value)))#define AO_F_OR(ptr, value) ((__typeof__(*(ptr)))__sync_fetch_and_or((ptr), (value)))#define AO_F_AND(ptr, value) ((__typeof__(*(ptr)))__sync_fetch_and_and((ptr), (value)))#define AO_F_XOR(ptr, value) ((__typeof__(*(ptr)))__sync_fetch_and_xor((ptr), (value)))
#else#error "can not supported atomic operation by gcc(v4.0.0+) buildin function."
#endif /* if (GCC_VERSION >= 40100) */
/* 忽略返回值,算术和位操作 */
#define AO_INC(ptr) ((void)AO_ADD_F((ptr), 1))
#define AO_DEC(ptr) ((void)AO_SUB_F((ptr), 1))
#define AO_ADD(ptr, val) ((void)AO_ADD_F((ptr), (val)))
#define AO_SUB(ptr, val) ((void)AO_SUB_F((ptr), (val)))
#define AO_OR(ptr, val) ((void)AO_OR_F((ptr), (val)))
#define AO_AND(ptr, val) ((void)AO_AND_F((ptr), (val)))
#define AO_XOR(ptr, val) ((void)AO_XOR_F((ptr), (val)))
/* 通过掩码,设置某个位为1,并返还新的值 */
#define AO_BIT_ON(ptr, mask) AO_OR_F((ptr), (mask))
/* 通过掩码,设置某个位为0,并返还新的值 */
#define AO_BIT_OFF(ptr, mask) AO_AND_F((ptr), ~(mask))
/* 通过掩码,交换某个位,1变0,0变1,并返还新的值 */
#define AO_BIT_XCHG(ptr, mask) AO_XOR_F((ptr), (mask))

###普通汇编指令 以加法指令操作实现 x = x + n为例 ,gcc编译出来的汇编形式上如下:

...
movl 0xc(%ebp), %eax
addl $n, %eax
movl %eax, 0xc(%ebp)
...

可以看出,实现这条c语句,需要先将x所在内存0xc(%ebp)中的值装载到寄存器%eax中,然后用addl指令进行与一个立即数$n进行加操作,之后再寄存器中的结果装载回原内存中。如果在时序上又另一个线程也操作该内存中的值,且在指令addl $n, %eax完成之后,时间片切换到了另一个线程中,该线程进行了该内存的修改操作,而且还会在后续的操作中使用,这个时候发生又发生时间片切换,切回到原线程中,进行movl %eax, 0xc(%ebp)指令覆盖了前一个线程修改内容,如果在这时再切换到另一个线程中,该线程就会使用到一个错误的值进行后续的操作。 ###gcc原子汇编指令 仍然以加法指令操作实现 x = x + n为例 ,gcc编译出来的原子汇编形式上如下:

...
mov $0x1,%eax
lock xadd %eax,-0x4(%rbp)
mov %eax,-0x4(%rbp)
...

gcc的原子操作是内建函数通过汇编实现的,统一命名以__sync_xxx()起头,原子操作做了什么事情呢?原子操作的原理都是通过汇编指令lock在各种xaddcmpxchgxchg指令前进行锁定操作内存的总线,并将上述的普通3条指令的操作合并为一条操作,因为内存与cpu都是通过总线进行数据交换,所以即使其它cpu核也同时(真正意义上的多线程,而不是单核上的时间片切换)要对该内存的存取,也要等待。(因为我不是低层开发人员,所以具体时序和动作我不是太了解,只能以应用层的锁动作理解这里的总线锁,如果你了解,请更正),而被锁总线的单核应该不会进行时间片切换,直到该指令完成。 ###优化带来语句倒置 除了多线程操作同一个内存时会发生数据的一致性错误,因为编译器的优化问题也会造成数据一致性问题。如果你的原意要进行如下的操作:

int a = 0;
int b = 0;
void A() {a = 1; b = 2;
}
void B() {if (b > 0)printf("a :%d\n", a);
}

那么经过编译器的优化,A()中的两条复制语句可能被调换顺序,如果两个线程分别同时执行A()和B(),那么因为这个原因,B()可能输出1,也可能输出0;解决方法是让a = 1一定在b = 2执行,那么在两者之间插入内存栅栏__sync_synchronize()可以保证先后次序。(因为我对这样的优化发生情况不是很明了,故这里不能详细的描述这样的优化对同线程产生的影响) ###volatile关键字与原子 原子操作的内存,要保证其内容已定是存取最新的,而不是cache中的数据,所以要用volatile关键字表明,这样每次存取cpu直接存取内存,而非cache中的数据,我们定义一个原子类型:

#ifndef AO_T
typedef volatile long AO_T;
#endif

##原子操作与普通C语句的等效操作

这里用上面定义的宏说明原子操作,等效的C语言非原子的操作为了保证一致性,我们使用lock()unlock这个伪语句表示锁的加锁和解锁。当然原子操作要比应用层加锁快了太多太多。

内存栅栏使用

int a = 0;
barrier();
int b = 2;

保证a的复制在b的复制前执行

原子获取

int a = 5;
int b = AO_GET(&a); //b==5;

int a = 5;
lock();
int b = a;
unlock();

保证读取a的值是内存中的值,而不是寄存器或cache中的值 ###原子设置

int a = 0;
AO_SET(&a, 10); //a==10;

int a = 0;
lock();
a = 10;
unlock();

###原子交换

int a = 10;
AO_SWAP(&a, 9);

int a = 10;
lock();
if (a != 9)a = 9;
unlock();

###原子比较交换

int a = 10;
int b = AO_CAS(&a, 10, 9); //b==10, a==9;
int c = AO_CAS(&a, 9, 8); //c==8, a==10;

int a = 10;
int b = 0;
int c = 0;
lock();
if (a == 10) {b = a;a = 9;
} else {b = 10;
}
unlock();
lock();
if (a == 9) {b = a;a = 8;
} else {b = 9;
}
unlock();

AO_CASB()的逻辑与AO_CAS()一致,只是返还一个真假值判断是否发生了交换,就不再赘诉了。 ###原子清零

int a = 10;
AO_CLEAR(&a); //a==0;

int a = 10;
lock();
a = 0;
unlock();

###先操作后使用的加减运算和逻辑运算

  • 先加一个数,再使用和值 AO_xxx_F()中的F表示fetch提取的意思

int a = 1;
int b = AO_ADD_F(&a, 10);//a==11, b==11

int a = 1;
int b = 0;
lock();
a += 10;
b = a;
unlock();

  • 其它的运算(减,或,与,异或)与加法操作逻辑一样,就不再赘诉了 ###先使用后操作的加减运算与逻辑运算
  • 使用原值,后加上一个数

int a = 1;
int b = AO_F_ADD(&a, 10);//a==11, b==1

int a = 1;
int b = 0;
lock();
b = a;
a += 10;
unlock();

##何时使用原子操作最合适

原子操作最合适用来管理状态,而且最好是程序发现状态不符合自己要求是,可以忽略这个错误,继续运行,或稍后在此尝试。比如我们使用一个local static变量存储当前系统有多少个cpu核,以备给出一些策略,比如以后我们要实现的自旋锁中的休眠。代码如下:

long GetCPUCores()
{static long g_CPUCores &#61; 0;long gcpus &#61; -1;/*原子获取&#xff0c;如果没有设置过&#xff0c;则继续&#xff0c;否则返回这个值*/if (likely((gcpus &#61; AO_GET(&g_CPUCores)) !&#61; -1)) {return gcpus;}gcpus &#61; sysconf(_SC_NPROCESSORS_CONF);if (unlikely(gcpus <0)) {printf("Get number of CPU failure : %s", strerror(errno));abort();}/*原子设置*/AO_SET(&g_CPUCores, gcpus);return gcpus;
}

如果有多个线程同时调用&#xff0c;或单个线程多次调用&#xff0c;我们都可以保证g_CPUCores中数据的有效性&#xff0c;不会出现获取到一个大于0到假值导致后续的逻辑错误。而且这样的设计&#xff0c;还可以提高效率&#xff0c;如果获取的系统参数是一个像

#ifdef __APPLE__gtid &#61; syscall(SYS_thread_selfid);
#elsegtid &#61; syscall(SYS_gettid);
#endif

的真正的系统调用&#xff0c;那么在结果固定的情况下&#xff0c;代价是昂贵的&#xff0c;因为程序必须要发起中断服务&#xff0c;切换到内核空间调用代码为SYS_thread_selfidSYS_gettid的中段服务&#xff0c;从而得到线程ID&#xff08;线程是一个轻量级的进程&#xff0c;只不过它的堆空间与其它线程共享&#xff0c;而不是进程那样是彼此独立的&#xff0c;我以后会在此细谈这个ID值的运用&#xff09;。

##使用原子操作

改进上一篇文章中提及的结构魔数操作

上一节我们说过&#xff0c;使用带魔数字段结构的函数通过判断、修改魔数做出相应的操作&#xff0c;试想如果两个线程同时操作魔数字段&#xff0c;肯定会带来冲突&#xff0c;所以我们将其对应的非原子操作&#xff0c;改为原子操作&#xff0c;代码如下&#xff1a;

/** 魔数* 结构体中设置一个magic的成员变量&#xff0c;已检查结构体是否被正确初始化*/
#if !defined(OBJMAGIC)#define OBJMAGIC (0xfedcba98)
#endif/*原子的设置魔数*/
#undef REFOBJ
#define REFOBJ(obj) \({ \int _old &#61; 0; \bool _ret &#61; false; \if (likely((obj))) { \_old &#61; AO_SWAP(&(obj)->magic, OBJMAGIC); \} \_ret &#61; (_old &#61;&#61; OBJMAGIC ? false : true); \_ret; \})/*原子的重置魔数*/
#undef UNREFOBJ
#define UNREFOBJ(obj) \({ \bool _ret &#61; false; \if (likely((obj))) { \_ret &#61; AO_CASB(&(obj)->magic, OBJMAGIC, 0); \} \_ret; \})/*原子的验证魔数*/
#undef ISOBJ
#define ISOBJ(obj) ((obj) && AO_GET(&(obj)->magic) &#61;&#61; OBJMAGIC)/*断言魔数*/
#undef ASSERTOBJ
#define ASSERTOBJ(obj) (assert(ISOBJ((obj))))

其实这样的运用也不能100&#xff05;的保证多线程下数据的一致性&#xff0c;比如两个线程&#xff21;和&#xff22;&#xff0c;同时在操作一个结构体&#xff34;&#xff1a; ###原子操作

  1. 初始化操作&#xff0c;initT():

void initT(T *t) {REFOBJ(t);//other initial operate
}

  1. 处理数据操作&#xff0c;dealT():

void dealT(T *t) {if(!ISOBJ(t)) return;//other deal operate
}

  1. 销毁数据&#xff0c;destroyT():

void destroy(T *t) {if(!UNREF(t)) return;//other destroy
}

###原子操作与时序

  • 考虑下列时序&#xff1a;
  1. A刚完成initT ()中的REFOBJ ()语句&#xff0c;将要真正的初始化T的其它的字段&#xff0c;这时切换到B&#xff1b;
  2. B也调用initT ()中的REFOBJ ()语句&#xff0c;发现结构体的初始化标志已经设置了&#xff0c;则返回并切换到A&#xff1b;
  3. A开始真正的初始化相关字段&#xff0c;在未处理完成时有切回了B&#xff1b;
  4. B调用dealT()开始处理其它的字段了&#xff0c;结果当然是全处理的是脏数据。
  • 再考虑下列时序&#xff1a;
  1. A和B都使用T交错的操作了T一段时间&#xff0c;A和B都想销毁T持有的数据而调用destroy()&#xff1b;
  2. 假设A先进入destroy()&#xff0c;然后在UNREF ()调用之前切换到B&#xff1b;
  3. B进入destroy(), 并成功的调用UNREF ()&#xff1b;
  4. 在后续的操作中&#xff0c;不论何时发生切换都不会造成数据重复销毁。

上面情况出现的根本原因就是原子操作不原子&#xff0c;因为你企图使用这样的原子操作&#xff0c;进行非原子的多步骤的字段初始化操作&#xff0c;这是不会成功的。所以在你使用原子操作时&#xff0c;一定要考虑线程切换带来的时序问题和你的原子操作能不能使你的操作原子的进行。

##下一步我们做什么

我们将使用原子操作实现一个原子锁&#xff0c;并说明什么情况下应该使用原子锁&#xff0c;什么情况下不应该使用原子锁。敬请期待哦。


转:https://my.oschina.net/u/1160717/blog/725497



推荐阅读
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 本文详细介绍了如何使用MySQL来显示SQL语句的执行时间,并通过MySQL Query Profiler获取CPU和内存使用量以及系统锁和表锁的时间。同时介绍了效能分析的三种方法:瓶颈分析、工作负载分析和基于比率的分析。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • 2018年人工智能大数据的爆发,学Java还是Python?
    本文介绍了2018年人工智能大数据的爆发以及学习Java和Python的相关知识。在人工智能和大数据时代,Java和Python这两门编程语言都很优秀且火爆。选择学习哪门语言要根据个人兴趣爱好来决定。Python是一门拥有简洁语法的高级编程语言,容易上手。其特色之一是强制使用空白符作为语句缩进,使得新手可以快速上手。目前,Python在人工智能领域有着广泛的应用。如果对Java、Python或大数据感兴趣,欢迎加入qq群458345782。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 基于事件驱动的并发编程及其消息通信机制的同步与异步、阻塞与非阻塞、IO模型的分类
    本文介绍了基于事件驱动的并发编程中的消息通信机制,包括同步和异步的概念及其区别,阻塞和非阻塞的状态,以及IO模型的分类。同步阻塞IO、同步非阻塞IO、异步阻塞IO和异步非阻塞IO等不同的IO模型被详细解释。这些概念和模型对于理解并发编程中的消息通信和IO操作具有重要意义。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 本文讨论了在iOS平台中的Metal框架中,对于if语句中的判断条件的限制和处理方式。作者提到了在Metal shader中,判断条件不能写得太长太复杂,否则可能导致程序停留或没有响应。作者还分享了自己的经验,建议在CPU端进行处理,以避免出现问题。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
author-avatar
mobiledu2502856247
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有