__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. 【IBM】netperf 与网络性能测量

    netperf 与网络性能测量 汤凯2004 年 7 月 01 日发布 WeiboGoogle+用电子邮件发送本页面 2 在构建或管理一个网络系统时,我们更多的是关心网络的可用性,即网络是否连通,而对 ...

  2. 测usb读写

    dd if=/dev/sda of=/dev/null bs=1M count=1000每次测完 清一下 memory cacheecho 3 > /proc/sys/vm/drop_cache ...

  3. 有关fgets和fcntl的讨论-待整理更新

    问题引出 一个client程序:select 超时监听 sockfd套接字 和 STDIN_FILENO标准输入:若sockfd可读则接收server报文:若标准输入可读(按下回车),则开始用fget ...

  4. SUSE12 操作系统安装

    今天开发同事需要一个客户的SUSE环境,原来没有安装过这个操作系统,网络配置方面有些问题见下一篇 镜像:SLE-12-SP3-Server-DVD-x86_64-GM-DVD1.iso 安装过程: 选 ...

  5. php5.6 + apache2.4+mysql5.7 配置

    ----------------------------------------------------- ★软件工具:(下载时注意下载相应版本,不同版本安装细节可能会有差异!!) 1>http ...

  6. TensorFlow解析常量、变量和占位符

    TensorFlow解析常量.变量和占位符 最基本的 TensorFlow 提供了一个库来定义和执行对张量的各种数学运算.张量,可理解为一个 n 维矩阵,所有类型的数据,包括标量.矢量和矩阵等都是特殊 ...

  7. RGB Color Codes Chart

    RGB Color Codes Chart RGB颜色空间 RGB颜色空间或RGB颜色系统,从红色.绿色和蓝色的组合中构造所有颜色. 红色.绿色和蓝色各使用8位,它们的整数值从0到255.这使得256 ...

  8. 编译原理-DFA的化简(最小化)

    对于给定的DFA    M,寻找一个状态数比M小的DFA    M'使得L(M)=L(M') 1.状态的等价性: 假设s和t为M的两个状态 ①若分别从状态s和状态t出发都能读出某个字α而停止于终态,则 ...

  9. 错误档案2:MySQL8.0连接C3P0的问题

    >>>跳过BB,空降正文<<< 目录 前言 问题出现 解决方法 结论 前言 大家好呀,我是 白墨,一个热爱学习与划水的矛盾体. 前两天在使用C3P0连接池时遇到问题 ...

  10. 狂神说JUC学习笔记(一)

    狂神说JUC的原版笔记: 链接:https://pan.baidu.com/s/12zrGI4JyZhmkQh0cqEO4BA 提取码:d65c 我的笔记在狂神的笔记上增加了一些知识点或者做了些许修改 ...