__sync_fetch_and_add函数(Redis源码学习)
__sync_fetch_and_add函数(Redis源码学习)
在学习redis-3.0源码中的sds文件时,看到里面有如下的C代码,之前从未接触过,所以为了全面学习redis源码,追根溯源,学习一下__sync_fetch_and_add的系列函数:
#define update_zmalloc_stat_add(__n) __sync_add_and_fetch(&used_memory, (__n))
在网上查找相关 __sync_add_and_fetch 函数的知识点,基本都是一样的内容,于是总结如下。
1.背景由来
实现多线程环境下的计数器操作,统计相关事件的次数. 当然我们知道,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,
2.系列函数
__sync_fetch_and_add系列一共有十二个函数,有加/减/与/或/异或/等函数的原子性操作函数,__sync_fetch_and_add,顾名思义,先fetch,然后自加,返回的是自加以前的值。以count = 4为例,调用__sync_fetch_and_add(&count,1)之后,返回值是4,然后,count变成了5.
简单验证代码如下sync_fetch_add.c:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv){
int count = 4;
printf("111 count:%d\n",count);
int retval = __sync_fetch_and_add(&count,10);
printf("222 retval:%d\n",retval);
printf("222 count:%d\n",count);
return 0;
}
linux 系统中命令行执行:gdb -g -o sync_fetch_add sync_fetch_add.c
得到可执行文件,执行后得到如下结果:
./sync_fetch_add
111 count:4
222 retval:4
222 count:14
其他函数可以自行验证。
有__sync_fetch_and_add,自然也就有__sync_add_and_fetch,呵呵这个的意思就很清楚了,先自加,在返回。他们的关系与i++和++i的关系是一样的。有了这个函数,对于多线程对全局变量进行自加,我们就再也不用理线程锁了。下面这行代码,和上面被pthread_mutex保护的那行代码作用是一样的,而且也是线程安全的。
在用gcc编译的时候要加上选项 -march=i686,我在执行上面代码时,gcc没加该参数,使用到的版本gcc version 4.4.7 20120313 , 上面代码能正常运行通过。
下面是这群函数的全部,无非是先fetch再运算,或者先运算再fetch。
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);
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, ...)
这两组函数的区别在于第一组返回更新前的值,第二组返回更新后的值。
看网上有大师的代码测试例子Alexander Sandler,现拷贝为 sync_fetch2.c 文件如下并验证执行结果:
#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>
#define INC_TO 1000000 // one million...
int global_int = 0;
pid_t gettid( void )
{
return syscall( __NR_gettid );
}
void *thread_routine( void *arg )
{
int i;
int proc_num = (int)(long)arg;
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;
}
for (i = 0; i < INC_TO; i++)
{
// global_int++;
__sync_fetch_and_add( &global_int, 1 );
}
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 = (pthread_t *)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;
}
上面代码在RHEL6.9中编译:g++ -g -o sync_fetch2 sync_fetch2.c -lpthread
执行结果为:
./sync_fetch2
Starting 4 threads...
After doing all the math, global_int value is: 4000000
Expected value is: 4000000
如果将上面thread_routine函数中的这两句换一下,直接用变量加加,则每次执行都得到不一样的值
global_int++;
// __sync_fetch_and_add( &global_int, 1 );
修改后得到结果如下:
$./sync_fetch2
Starting 4 threads...
After doing all the math, global_int value is: 1428371
Expected value is: 4000000
$ ./sync_fetch2
Starting 4 threads...
After doing all the math, global_int value is: 2479197
Expected value is: 4000000
3.小结
可以从代码验证中看到 __sync_fetch_and_add 函数的作用,在多线程中,对简单的变量运算能保证结果的正确,至于其他函数,参考上面代码,读者可以自行验证。
另外基于上面例子,有人修改代码,加上执行消耗时间,通过__sync_fetch_and_add和加锁机制的对比,发现__sync_fetch_and_add比加解锁机制快了6-7倍,执行速度还是很快的,因为涉及到汇编代码,后续有机会会再学习验证。
本人才疏学浅,错误不当之处,请批评指正。
如果文章对您有一点点用处,我会很高兴能帮到您。多谢关注推荐和转发,谢谢!
参考网址:
http://www.alexonlinux.com/multithreaded-simple-data-type-access-and-atomic-variables
https://blog.csdn.net/i_am_jojo/article/details/7591743
https://www.zhihu.com/question/280022939
https://blog.csdn.net/long2324066440/article/details/72784084
__sync_fetch_and_add函数(Redis源码学习)的更多相关文章
- Redis源码学习:字符串
Redis源码学习:字符串 1.初识SDS 1.1 SDS定义 Redis定义了一个叫做sdshdr(SDS or simple dynamic string)的数据结构.SDS不仅用于 保存字符串, ...
- Redis源码学习:Lua脚本
Redis源码学习:Lua脚本 1.Sublime Text配置 我是在Win7下,用Sublime Text + Cygwin开发的,配置方法请参考<Sublime Text 3下C/C++开 ...
- redis源码学习之slowlog
目录 背景 环境说明 redis执行命令流程 记录slowlog源码分析 制造一条slowlog slowlog分析 1.slowlog如何开启 2.slowlog数量限制 3.slowlog中的耗时 ...
- 柔性数组(Redis源码学习)
柔性数组(Redis源码学习) 1. 问题背景 在阅读Redis源码中的字符串有如下结构,在sizeof(struct sdshdr)得到结果为8,在后续内存申请和计算中也用到.其实在工作中有遇到过这 ...
- redis源码学习之工作流程初探
目录 背景 环境准备 下载redis源码 下载Visual Studio Visual Studio打开redis源码 启动过程分析 调用关系图 事件循环分析 工作模型 代码分析 动画演示 网络模块 ...
- redis源码学习之lua执行原理
聊聊redis执行lua原理 从一次面试场景说起 "看你简历上写的精通redis" "额,还可以啦" "那你说说redis执行lua脚本的原理&q ...
- Redis源码学习-Master&Slave的命令交互
0. 写在前面 Version Redis2.2.2 Redis中可以支持主从结构,本文主要从master和slave的心跳机制出发(PING),分析redis的命令行交互. 在Redis中,serv ...
- Redis源码学习1-sds.c
https://github.com/huangz1990/redis-3.0-annotated/blob/unstable/src/sds.c#L120 /* SDSLib, A C dynami ...
- redis源码学习_字典
redis中字典有以下要点: (1)它就是一个键值对,对于hash冲突的处理采用了头插法的链式存储来解决. (2)对rehash,扩展就是取第一个大于等于used * 2的2 ^ n的数作为新的has ...
随机推荐
- shell脚本就是由Shell命令组成的执行文件,将一些命令整合到一个文件中,进行处理业务逻辑,脚本不用编译即可运行。它通过解释器解释运行,所以速度相对来说比较慢。
shell脚本?在说什么是shell脚本之前,先说说什么是shell. shell是外壳的意思,就是操作系统的外壳.我们可以通过shell命令来操作和控制操作系统,比如Linux中的Shell命令就包 ...
- 2017-11-20 崂应工作总结,含LTC3780模块分析,含运放原理
学习了运算放大器的分类 运放的单点输入 差动模式 共模抑制输入模式 反相位比例运放 正相比例运放 电压跟随器 运放的放大比例计算 LTC3780模块的原理 因为: R19 这个电阻不确定他的接法 暂 ...
- Mybatis 逆向工程使用(Day_17)
有三个方法可以解决所有的问题:接受,改变,离开.不能接受那就改变,不能改变,那就离开. [何为逆向工程?] 众 所 周 知 ,正向工程就是通过JAVA类的设置来生成对应的数据库表. MyBatis逆向 ...
- 重新整理 .net core 实践篇—————服务与配置之间[十一二]
前言 前面基本介绍了,官方对于asp .net core 设计配置和设计服务的框架的一些思路.看下服务和配置之间是如何联系的吧. 正文 服务: public interface ISelfServic ...
- python基础知识笔记(二) (出现语法以及颜色问题)
以下是在学习Python时需要尤其要注意的点: 1.程序中出现中文,运行的时候出现如下错误: SyntaxError: Non-UTF-8 code starting with '\xc1' 则是编码 ...
- C# 将DLL制作CAB包并在浏览器下载,自动安装。(Activex)(包括ie打开cab包一直弹出用户账户控制,确定之后无反应的解决办法。)
制作Activex程序网上有很多方法我就不说了,我的业务主要做的就是将DLL打包成CAB供浏览器下载. 下面制作证书,以及制作cab包需要用到一些工具.我将工具包上传到自己的博客园里了,以供大家下载. ...
- CMOS图像传感器同时感知和处理光学图像
CMOS图像传感器同时感知和处理光学图像 概述 近年来,机器视觉技术有了巨大的飞跃,现在已经成为各种智能系统的一个组成部分,包括自主车辆和机器人.通常,视觉信息由基于帧的摄像机捕获,转换成数字格式,然 ...
- nvGraph-NVIDIA图形库
nvGraph-NVIDIA图形库 数据分析是高性能计算的不断增长的应用.许多高级数据分析问题可以称为图形问题.反过来,当今许多常见的图形问题也可以称为稀疏线性代数.这是nvGraph的动机,它利用G ...
- 编写HSA内核
编写HSA内核 介绍 HSA提供类似于OpenCL的执行模型.指令由一组硬件线程并行执行.在某种程度上,这类似于 单指令多数据(SIMD)模型,但具有这样的便利:细粒度调度对于程序员而言是隐藏的,而不 ...
- 孟老板 BaseAdapter封装(五) ListAdapter
BaseAdapter封装(一) 简单封装 BaseAdapter封装(二) Header,footer BaseAdapter封装(三) 空数据占位图 BaseAdapter封装(四) PageHe ...