__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源码学习)的更多相关文章

  1. Redis源码学习:字符串

    Redis源码学习:字符串 1.初识SDS 1.1 SDS定义 Redis定义了一个叫做sdshdr(SDS or simple dynamic string)的数据结构.SDS不仅用于 保存字符串, ...

  2. Redis源码学习:Lua脚本

    Redis源码学习:Lua脚本 1.Sublime Text配置 我是在Win7下,用Sublime Text + Cygwin开发的,配置方法请参考<Sublime Text 3下C/C++开 ...

  3. redis源码学习之slowlog

    目录 背景 环境说明 redis执行命令流程 记录slowlog源码分析 制造一条slowlog slowlog分析 1.slowlog如何开启 2.slowlog数量限制 3.slowlog中的耗时 ...

  4. 柔性数组(Redis源码学习)

    柔性数组(Redis源码学习) 1. 问题背景 在阅读Redis源码中的字符串有如下结构,在sizeof(struct sdshdr)得到结果为8,在后续内存申请和计算中也用到.其实在工作中有遇到过这 ...

  5. redis源码学习之工作流程初探

    目录 背景 环境准备 下载redis源码 下载Visual Studio Visual Studio打开redis源码 启动过程分析 调用关系图 事件循环分析 工作模型 代码分析 动画演示 网络模块 ...

  6. redis源码学习之lua执行原理

    聊聊redis执行lua原理 从一次面试场景说起   "看你简历上写的精通redis" "额,还可以啦" "那你说说redis执行lua脚本的原理&q ...

  7. Redis源码学习-Master&Slave的命令交互

    0. 写在前面 Version Redis2.2.2 Redis中可以支持主从结构,本文主要从master和slave的心跳机制出发(PING),分析redis的命令行交互. 在Redis中,serv ...

  8. Redis源码学习1-sds.c

    https://github.com/huangz1990/redis-3.0-annotated/blob/unstable/src/sds.c#L120 /* SDSLib, A C dynami ...

  9. redis源码学习_字典

    redis中字典有以下要点: (1)它就是一个键值对,对于hash冲突的处理采用了头插法的链式存储来解决. (2)对rehash,扩展就是取第一个大于等于used * 2的2 ^ n的数作为新的has ...

随机推荐

  1. Linux 系统中如何查看日志 (常用命令) tail -f

    Linux 系统中如何查看日志 (常用命令)  tail -f 日志文件 日 志 文 件 说 明 /var/log/message 系统启动后的信息和错误日志,是Red Hat Linux中最常用的日 ...

  2. AD命令获取计算机、用户相关信息

    1. 获取AD用户相关信息(用户名.创建日期.最后修改密码日期.最后登录日期) Get AD users, Name/Created Date/Last change passwd Date/Last ...

  3. JQuery 基础之基本选择器

    1.什么是jQuery选择器: jQuery选择器继承了CSS与Path语言的部分语法,允许通过标签名.属性名或内容对DOM元素进行快速.准确的选择,而不必担心浏览器的兼容性,通过jQuery选择器对 ...

  4. Jmeter- 笔记1 - 理论知识

    为什么不用loadrunner,lonadrunner免费最大并发用户50,再往上就要买license了. 性能输出结果不是bug 假如调试脚本没有出错,但运行脚本时,可能前期没有问题,但到后期偶尔/ ...

  5. 永远的Ace 实验五 团队作业2:XXX企业设施设备云上资料室

    项目 内容 课程班级博客链接 https://edu.cnblogs.com/campus/xbsf/2018CST/ 这个作业要求链接 https://www.cnblogs.com/nwnu-da ...

  6. TVM性能评估分析(一)

    TVM性能评估分析(一) System Overview AutoTVM vs Auto-scheduler Table 1. Workflow Comparision Figure 1. Searc ...

  7. 2020年Yann Lecun深度学习笔记(下)

    2020年Yann Lecun深度学习笔记(下)

  8. 工作流中容器化的依赖注入!Activiti集成CDI实现工作流的可配置型和可扩展型

    Activiti工作流集成CDI简介 activiti-cdi模块提供activiti的可配置型和cdi扩展 activiti-cdi的特性: 支持 @BusinessProcessScoped be ...

  9. Java面试必知必会:基础

    面试考察的知识点多而杂,要完全掌握需要花费大量的时间和精力.但是面试中经常被问到的知识点却没有多少,你完全可以用 20% 的时间去掌握 80% 常问的知识点. 一.基础 包括: 杂七杂八 面向对象 数 ...

  10. 性能监控工具之Grafana+Prometheus+Exporters

    在本模块中,我将把几个常用的监控部分给梳理一下.前面我们提到过,在性能监控图谱中,有操作系统.应用服务器.中间件.队列.缓存.数据库.网络.前端.负载均衡.Web 服务器.存储.代码等很多需要监控的点 ...