您的位置:首页 > Web前端

多线程条件下的计数器__原子的加/减 __sync_fetch_and_add

2016-11-30 09:40 211 查看
多线程条件下的计数器是服务器开发的常用操作,比如异步请求sessionid的活动,通常我们会用:

1、加锁取sessionid

2、分段取sessionid (在初始化阶段完成多线程分段取sessionid,这种使我们现在使用的方式,但是计数器是不能分段的)

当然我们知道,count++这种操作不是原子的。一个自加操作,本质是分成三步的:
1、 从缓存取到寄存器

2、 在寄存器加1

3、 存入缓存。

由于时序的因素,多个线程操作同一个全局变量,会出现问题。这也是并发编程的难点。在目前多核条件下,这种困境会越来越彰显出来。

最简单的处理办法就是加锁保护,这也是我最初的解决方案。看下面的代码:

pthread_mutex_t count_lock = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_lock(&count_lock);

global_int++;

pthread_mutex_unlock(&count_lock);

后来在网上查找资料,找到了__sync_fetch_and_add系列的命令,发现这个系列命令讲的最好的一篇文章,英文好的同学可以直接去看原文。Multithreaded simple data type access and atomic variables

__sync_fetch_and_add系列一共有十二个函数,有加 /减 /与 /或 /异或 /等函数的原子性操作函数,__sync_fetch_and_add,顾名思义,先fetch,然后自加,返回的是自加以前的值。以count = 4为例,调用__sync_fetch_and_add(&count,1),之后,返回值是4,然后,count变成了5。

有__sync_fetch_and_add,自然也就有__sync_add_and_fetch,这个的意思就很清楚了,先自加,在返回。他们哥俩的关系与i++和++i的关系是一样的。

有了这个宝贝函数,我们就有新的解决办法了。对于多线程对全局变量进行自加,我们就再也不用理线程锁了。下面这行代码,和上面被pthread_mutex保护的那行代码作用是一样的,而且也是线程安全的。

__sync_fetch_and_add( &global_int, 1 );

下面是这群函数的全家福,大家看名字就知道是这些函数是干啥的了。

type __sync_fetch_and_add (type *ptr, type value);             //加  先取值,再做++

type __sync_fetch_and_sub (type *ptr, type value);             //减

type __sync_fetch_and_or (type *ptr, type value);                //或

type __sync_fetch_and_and (type *ptr, type value);             //与

type __sync_fetch_and_xor (type *ptr, type value);

type __sync_fetch_and_nand (type *ptr, type value);

type __sync_add_and_fetch (type *ptr, type value);

type __sync_sub_and_fetch (type *ptr, type value);

type __sync_or_and_fetch (type *ptr, type value);

type __sync_and_and_fetch (type *ptr, type value);

type __sync_xor_and_fetch (type *ptr, type value);

type __sync_nand_and_fetch (type *ptr, type value);

需要提及的是,这个type不能够瞎搞(type只能是int, long ,long long(及对应unsigned类型)。

 

下面看下__sync_fetch_and_add反汇编出来的指令,

804889d: f0 83 05 50 a0 04 08 lock addl $0x1,0x804a050

我们看到了,addl前面有个lock,这行汇编指令码前面是f0开头,f0叫做指令前缀,Richard Blum

老爷子将指令前缀分成了四类,有兴趣的同学可以看下。其实我也没看懂,intel的指令集太厚了,没空看。总之老爷子解释了,lock前缀的意思是对内存区域的排他性访问。

? Lock and repeat prefixes

? Segment override and branch hint prefixes

? Operand size override prefix

? Address size override prefix

前文提到,lock是锁FSB,前端串行总线,front serial bus,这个FSB是处理器和RAM之间的总线,锁住了它,就能阻止其他处理器或者core从RAM获取数据。当然这种操作是比较费的,只能操作小的内存可以这样做,想想我们有memcpy ,如果操作一大片内存,锁内存,那么代价就太昂贵了。所以前文提到的_sync_fetch_add_add家族,type只能是int long ,long long(及对应unsigned类型)。

 

性能上__sync_fetch_and_add,完爆线程锁。

从测试结果上看, __sync_fetch_and_add,速度是线程锁的6~7倍。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息