http://blog.sina.com.cn/s/blog_6f5b220601013zw3.html

非常好的原子操作,不用加锁:__sync_fetch_and_add

GCC 提供的原子操作

gcc从4.1.2提供了__sync_*系列的built-in函数,用于提供加减和逻辑运算的原子操作。

其声明如下:

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可以是1,2,4或8字节长度的int类型,即:

int8_t / uint8_t
int16_t / uint16_t
int32_t / uint32_t
int64_t / uint64_t

后面的可扩展参数(...)用来指出哪些变量需要memory barrier,因为目前gcc实现的是full
barrier(类似于linux kernel
中的mb(),表示这个操作之前的所有内存操作不会被重排序到这个操作之后),所以可以略掉这个参数。


bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)

type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)

这两个函数提供原子的比较和交换,如果*ptr == oldval,就将newval写入*ptr,
第一个函数在相等并写入的情况下返回true.
第二个函数在返回操作之前的值。

__sync_synchronize (...)

发出一个full barrier.

关于memory
barrier,cpu会对我们的指令进行排序,一般说来会提高程序的效率,但有时候可能造成我们不希望得到的结果,举一个例子,比如我们有一个硬件设
备,它有4个寄存器,当你发出一个操作指令的时候,一个寄存器存的是你的操作指令(比如READ),两个寄存器存的是参数(比如是地址和size),最后
一个寄存器是控制寄存器,在所有的参数都设置好之后向其发出指令,设备开始读取参数,执行命令,程序可能如下:

    write1(dev.register_size,size);

write1(dev.register_addr,addr);

write1(dev.register_cmd,READ);

write1(dev.register_control,GO);

如果最后一条write1被换到了前几条语句之前,那么肯定不是我们所期望的,这时候我们可以在最后一条语句之前加入一个memory
barrier,强制cpu执行完前面的写入以后再执行最后一条:

write1(dev.register_size,size);

write1(dev.register_addr,addr);

write1(dev.register_cmd,READ);

__sync_synchronize();

write1(dev.register_control,GO);

memory barrier有几种类型:
    acquire
barrier : 不允许将barrier之后的内存读取指令移到barrier之前(linux
kernel中的wmb())。
    release
barrier : 不允许将barrier之前的内存读取指令移到barrier之后 (linux
kernel中的rmb())。
    full
barrier    :
以上两种barrier的合集(linux kernel中的mb())。

还有两个函数:

type __sync_lock_test_and_set (type *ptr, type value, ...)
  
将*ptr设为value并返回*ptr操作之前的值。

void __sync_lock_release (type *ptr, ...)
    
将*ptr置0

示例程序:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

static int count = 0;

void *test_func(void *arg)
{
        int i=0;

for(i=0;i<20000;++i){

__sync_fetch_and_add(&count,1);

}

return NULL;

}

int main(int argc, const char *argv[])

{
        pthread_t id[20];

int i = 0;

for(i=0;i<20;++i){

pthread_create(&id[i],NULL,test_func,NULL);

}

for(i=0;i<20;++i){

pthread_join(id[i],NULL);

}

printf("%d\n",count);

return 0;

}

参考:

1.
http://refspecs.freestandards.org/elf/IA64-SysV-psABI.pdf  
section 7.4

2.
http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html#Atomic-Builtins

感谢作者:http://blog.csdn.net/i_am_jojo/article/details/7591743

最近编码需要实现多线程环境下的计数器操作,统计相关事件的次数。下面是一些学习心得和体会。不敢妄称原创,基本是学习笔记。遇到相关的引用,我会致谢。

当然我们知道,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 );
   
下面是这群函数的全家福,大家看名字就知道是这些函数是干啥的了。

在用gcc编译的时候要加上选项
-march=i686
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不能够瞎搞。下面看下__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类型)。

下面提供了函数,是改造的Alexander
Sundler的原文,荣誉属于他,我只是学习他的代码,稍微改动了一点点。比较了两种方式的耗时情况。呵呵咱是菜鸟,不敢枉自剽窃大师作品。向大师致敬。

#define _GNU_SOURCE

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sched.h>
#include <linux/unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include<linux/types.h>
#include<time.h>

#define INC_TO 1000000 // one million...

__u64 rdtsc()
{
  __u32 lo,hi;

__asm__
__volatile__
    (
    
"rdtsc":"=a"(lo),"=d"(hi)
    );

return
(__u64)hi<<32|lo;
}

int global_int = 0;
pthread_mutex_t count_lock = PTHREAD_MUTEX_INITIALIZER;

pid_t gettid( void )
{
    return
syscall( __NR_gettid );
}

void *thread_routine( void *arg )
{
    int i;
    int proc_num
= (int)(long)arg;
    __u64 begin,
end;
    struct
timeval tv_begin,tv_end;
    __u64
timeinterval;
    cpu_set_t
set;

CPU_ZERO(
&set );
    CPU_SET(
proc_num, &set );

if
(sched_setaffinity( gettid(), sizeof( cpu_set_t ),
&set ))
    {
       
perror( "sched_setaffinity" );
       
return NULL;
    }

begin =
rdtsc();
   
gettimeofday(&tv_begin,NULL);
    for (i = 0;
i < INC_TO; i++)
    {
//    
global_int++;
       
__sync_fetch_and_add( &global_int, 1 );
    }
   
gettimeofday(&tv_end,NULL);
    end =
rdtsc();
    timeinterval
=(tv_end.tv_sec -
tv_begin.tv_sec)*1000000                   
+(tv_end.tv_usec - tv_begin.tv_usec);
   
fprintf(stderr,"proc_num :%d,__sync_fetch_and_add
cost            
%llu CPU cycle,cost %llu
us\n",                            
proc_num,end-begin,timeinterval);

return
NULL;
}

void *thread_routine2( void *arg )
{
    int i;
    int proc_num
= (int)(long)arg;
    __u64 begin,
end;

struct
timeval tv_begin,tv_end;
    __u64
timeinterval;
    cpu_set_t
set;

CPU_ZERO(
&set );
    CPU_SET(
proc_num, &set );

if
(sched_setaffinity( gettid(), sizeof( cpu_set_t ),
&set ))
    {
       
perror( "sched_setaffinity" );
       
return NULL;
    }

begin =
rdtsc();
   
gettimeofday(&tv_begin,NULL);

for(i =
0;i<INC_TO;i++)
    {
       
pthread_mutex_lock(&count_lock);
       
global_int++;
       
pthread_mutex_unlock(&count_lock);
    }

gettimeofday(&tv_end,NULL);
    end =
rdtsc();

timeinterval
=(tv_end.tv_sec -
tv_begin.tv_sec)*1000000                  
+(tv_end.tv_usec - tv_begin.tv_usec);
   
fprintf(stderr,"proc_num :%d,pthread lock cost %llu
CPU                   
cycle,cost %llu
us\n",proc_num,end-begin                   
,timeinterval);

return
NULL;
}
int main()
{
    int procs =
0;
    int i;
    pthread_t
*thrs;

//
Getting number of CPUs
    procs =
(int)sysconf( _SC_NPROCESSORS_ONLN );
    if (procs
< 0)
    {
       
perror( "sysconf" );
       
return -1;
    }

thrs =
malloc( sizeof( pthread_t ) * procs );
    if (thrs ==
NULL)
    {
       
perror( "malloc" );
       
return -1;
    }

printf(
"Starting %d threads...\n", procs );

for (i =
0; i < procs; i++)
    {
       
if (pthread_create( &thrs[i], NULL,
thread_routine,
           
(void *)(long)i ))
       
{
           
perror( "pthread_create" );
           
procs = i;
           
break;
       
}
    }

for (i =
0; i < procs; i++)
       
pthread_join( thrs[i], NULL );

free(
thrs );

printf(
"After doing all the math, global_int value
is:             
%d\n", global_int );
    printf(
"Expected value is: %d\n", INC_TO * procs );

return
0;
}
    
通过我的测试发现:

Starting 4 threads...
proc_num :2,no locker cost 27049544 CPU cycle,cost 12712 us
proc_num :0,no locker cost 27506750 CPU cycle,cost 12120 us
proc_num :1,no locker cost 28499000 CPU cycle,cost 13365 us
proc_num :3,no locker cost 27193093 CPU cycle,cost 12780 us
After doing all the math, global_int value is: 1169911
Expected value is: 4000000
Starting 4 threads...
proc_num :2,__sync_fetch_and_add cost 156602056 CPU cycle,cost
73603 us
proc_num :1,__sync_fetch_and_add cost 158414764 CPU cycle,cost
74456 us
proc_num :3,__sync_fetch_and_add cost 159065888 CPU cycle,cost
74763 us
proc_num :0,__sync_fetch_and_add cost 162621399 CPU cycle,cost
76426 us
After doing all the math, global_int value is: 4000000
Expected value is: 4000000

Starting 4 threads...
proc_num :1,pthread lock cost 992586450 CPU cycle,cost 466518
us
proc_num :3,pthread lock cost 1008482114 CPU cycle,cost 473998
us
proc_num :0,pthread lock cost 1018798886 CPU cycle,cost 478840
us
proc_num :2,pthread lock cost 1019083986 CPU cycle,cost 478980
us
After doing all the math, global_int value is: 4000000
Expected value is: 4000000
1 不加锁的情况下,不能返回正确的结果
  测试程序结果显示,正确结果为400万,实际为1169911.

2 线程锁和原子性自加都能返回正确的结果。

3 性能上__sync_fetch_and_add,完爆线程锁。
  从测试结果上看, __sync_fetch_and_add,速度是线程锁的6~7倍

[转] 多线程下变量-gcc原子操作 __sync_fetch_and_add等的更多相关文章

  1. Python_关于多线程下变量赋值取值的一点研究

    关于多线程下变量赋值取值的一点研究 by:授客 QQ:1033553122 1.代码实践1 #!/usr/bin/env python # -*- coding:utf-8 -*- __author_ ...

  2. 关于Spring在多线程下的个人疑问

    在Web开发中,不可避免的是需要遇到并发操作的,并发操作就有可能会引发我们的多线程安全问题.比如说,我们多线程下访问同一个变量并且有一个线程做出修改那么就会使得我们另外的线程在不知情的情况下被修改自己 ...

  3. Linux——多线程下解决生产消费者模型

    我们学习了操作系统,想必对生产消费者问题都不陌生.作为同步互斥问题的一个经典案例,生产消费者模型其实是解决实际问题的基础模型,解决很多的实际问题都会依赖于它.而此模型要解决最大的问题便是同步与互斥.而 ...

  4. 第10章 线程控制(5)_多线程下的fork

    6. 线程和fork 6.1 多线程下的fork (1)历史包袱 ①fork与多线程的协作性很差,这是POSIX系统操作系统的历史包袱. ②长期以来程序都是单线程的,fork运行正常,但引入线程这后, ...

  5. ASP.NET MVC Filters 4种默认过滤器的使用【附示例】 数据库常见死锁原因及处理 .NET源码中的链表 多线程下C#如何保证线程安全? .net实现支付宝在线支付 彻头彻尾理解单例模式与多线程 App.Config详解及读写操作 判断客户端是iOS还是Android,判断是不是在微信浏览器打开

    ASP.NET MVC Filters 4种默认过滤器的使用[附示例]   过滤器(Filters)的出现使得我们可以在ASP.NET MVC程序里更好的控制浏览器请求过来的URL,不是每个请求都会响 ...

  6. Java并发——原子变量和原子操作

    很多情况下我们只是需要一个简单的.高效的.线程安全的递增递减方案.注意,这里有三个条件:简单,意味着程序员尽可能少的操作底层或者实现起来要比较容易:高效意味着耗用资源要少,程序处理速度要快:线程安全也 ...

  7. C#多线程系列(3):原子操作

    本章主要讲述多线程竞争下的原子操作. 目录 知识点 竞争条件 线程同步 CPU时间片和上下文切换 阻塞 内核模式和用户模式 Interlocked 类 1,出现问题 2,Interlocked.Inc ...

  8. C#多线程下的调优

    一.原子操作 先看一段问题代码 /// <summary> /// 获取自增 /// </summary> public static void GetIncrement() ...

  9. python 类变量 在多线程下的共享与释放问题

    最近被多线程给坑了下,没意识到类变量在多线程下是共享的,还有一个就是没意识到 内存释放问题,导致越累越大 1.python 类变量 在多线程情况 下的 是共享的 2.python 类变量 在多线程情况 ...

随机推荐

  1. JQuery对单选框,复选框,下拉菜单的操作

    JSP <%@ page language="java" import="java.util.*" pageEncoding="utf-8&qu ...

  2. XAMPP 使用教程

        XAMPP 是一个把Apache网页服务器与PHP.Perl及MySQL集合在一起的安装包,允许用户可以在自己的电脑上轻易的建立网页服务器.使用 XAMPP 您可以轻松的在本机调试您的 PHP ...

  3. illegal mix of collcations表连接时非法的校对

    背景:旧表导入新表,新表里的字段是字符串类型 新表是int类型 两个字段通过字符串处理后相等 (准备left join 关联起来)报错 把int类型字段更改成varchar字符串类型后成功

  4. C#.NET Winform 通用开发框架

    C/S系统开发框架-企业版 V4.0 (Enterprise Edition) 简介: http://www.csframework.com/cs-framework-4.0.htm 视频下载: 百度 ...

  5. 优化MYSQL FILESORT

    用Explain分析SQL语句的时候,经常发现有的语句在Extra列会出现Using filesort,根据mysql官方文档对他的描述: 引用 MySQL must do an extra pass ...

  6. Servlet常用类

    javax.servlet.ServletConfig;javax.servlet.ServletException;javax.servlet.http.HttpServlet;javax.serv ...

  7. RichEdit中插入带背景色文本的一种思路

    uses RichEdit; function TextToRtf( // 将文本处理为RTF格式 mText: WideString // 输入文本 ): WideString; // 返回处理后的 ...

  8. 《how to design programs》15章 相互引用的数据定义

    由结构体组成的表与结构体中的表. 在用追溯形式建立家家谱树时,我们通常从某个后代除法,依次处理它的父母,组父母等.而构建树时,我们会不断添加谁是谁的孩子,而不是写出谁是谁的父母,从而建立一颗后代家谱树 ...

  9. COJ 0346 WZJ的旅行(二)更新动态树分治版本

    WZJ的旅行(二) 难度级别:D: 运行时间限制:3000ms: 运行空间限制:51200KB: 代码长度限制:2000000B 试题描述 时隔多日,WZJ又来到了幻想国旅行.幻想国由N个城市组成,由 ...

  10. 【转】NDK编译可执行文件在Android L中运行显示error: only position independent executables (PIE) are supported.失败问题解决办法。

    原文网址:http://blog.csdn.net/hxdanya/article/details/39371759 由于使用了NDK编译的可执行文件在应用中调用,在4.4及之前的版本上一直没出问题. ...