在学习 redis-3.0 源码中的 sds 文件时,看到里面有如下的 C 代码,之前从未接触过,所以为了全面学习 redis 源码,追根溯源,学习一下__sync_fetch_and_add 的系列函数:

  1. #define update_zmalloc_stat_add(__n) __sync_add_and_fetch(&used_memory, (__n))

在网上查找相关 __sync_add_and_fetch 函数的知识点,基本都是一样的内容,于是总结如下。

1. 背景由来

实现多线程环境下的计数器操作,统计相关事件的次数. 当然我们知道,count++ 这种操作不是原子的。一个自加操作,本质是分成三步的:

  1. 1 从缓存取到寄存器
  2. 2 在寄存器加1
  3. 3 存入缓存。

由于时序的因素,多个线程操作同一个全局变量,会出现问题。这也是并发编程的难点。在目前多核条件下,这种困境会越来越彰显出来。 最简单的处理办法就是加锁保护,这也是我最初的解决方案。看下面的代码:

  1. pthread_mutex_t count_lock = PTHREAD_MUTEX_INITIALIZER;
  2. pthread_mutex_lock(&count_lock);
  3. global_int++;
  4. 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:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main(int argc, char **argv){
  4. int count = 4;
  5. printf("111 count:%d\n",count);
  6. int retval = __sync_fetch_and_add(&count,10);
  7. printf("222 retval:%d\n",retval);
  8. printf("222 count:%d\n",count);
  9. return 0;
  10. }

linux 系统中命令行执行:gdb -g -o sync_fetch_add sync_fetch_add.c

得到可执行文件,执行后得到如下结果:

  1. ./sync_fetch_add
  2. 111 count:4
  3. 222 retval:4
  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。

  1. type __sync_fetch_and_add (type *ptr, type value);
  2. type __sync_fetch_and_sub (type *ptr, type value);
  3. type __sync_fetch_and_or (type *ptr, type value);
  4. type __sync_fetch_and_and (type *ptr, type value);
  5. type __sync_fetch_and_xor (type *ptr, type value);
  6. type __sync_fetch_and_nand (type *ptr, type value);
  7. type __sync_add_and_fetch (type *ptr, type value);
  8. type __sync_sub_and_fetch (type *ptr, type value);
  9. type __sync_or_and_fetch (type *ptr, type value);
  10. type __sync_and_and_fetch (type *ptr, type value);
  11. type __sync_xor_and_fetch (type *ptr, type value);
  12. type __sync_nand_and_fetch (type *ptr, type value);

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

其声明如下:

  1. type __sync_fetch_and_add (type * ptr, type value, ...)
  2. type __sync_fetch_and_sub (type * ptr, type value, ...)
  3. type __sync_fetch_and_or (type * ptr, type value, ...)
  4. type __sync_fetch_and_and (type * ptr, type value, ...)
  5. type __sync_fetch_and_xor (type * ptr, type value, ...)
  6. type __sync_fetch_and_nand (type * ptr, type value, ...)
  7. type __sync_add_and_fetch (type * ptr, type value, ...)
  8. type __sync_sub_and_fetch (type * ptr, type value, ...)
  9. type __sync_or_and_fetch (type * ptr, type value, ...)
  10. type __sync_and_and_fetch (type * ptr, type value, ...)
  11. type __sync_xor_and_fetch (type * ptr, type value, ...)
  12. type __sync_nand_and_fetch (type * ptr, type value, ...)

这两组函数的区别在于第一组返回更新前的值,第二组返回更新后的值。

看网上有大师的代码测试例子 Alexander Sandler,现拷贝为 sync_fetch2.c 文件如下并验证执行结果:

  1. #include <stdio.h>
  2. #include <pthread.h>
  3. #include <unistd.h>
  4. #include <stdlib.h>
  5. #include <sched.h>
  6. #include <linux/unistd.h>
  7. #include <sys/syscall.h>
  8. #include <errno.h>
  9. #define INC_TO 1000000 // one million...
  10. int global_int = 0;
  11. pid_t gettid( void )
  12. {
  13. return syscall( __NR_gettid );
  14. }
  15. void *thread_routine( void *arg )
  16. {
  17. int i;
  18. int proc_num = (int)(long)arg;
  19. cpu_set_t set;
  20. CPU_ZERO( &set );
  21. CPU_SET( proc_num, &set );
  22. if (sched_setaffinity( gettid(), sizeof( cpu_set_t ), &set ))
  23. {
  24. perror( "sched_setaffinity" );
  25. return NULL;
  26. }
  27. for (i = 0; i < INC_TO; i++)
  28. {
  29. // global_int++;
  30. __sync_fetch_and_add( &global_int, 1 );
  31. }
  32. return NULL;
  33. }
  34. int main()
  35. {
  36. int procs = 0;
  37. int i;
  38. pthread_t *thrs;
  39. // Getting number of CPUs
  40. procs = (int)sysconf( _SC_NPROCESSORS_ONLN );
  41. if (procs < 0)
  42. {
  43. perror( "sysconf" );
  44. return -1;
  45. }
  46. thrs = (pthread_t *)malloc( (sizeof( pthread_t )) * procs );
  47. if (thrs == NULL)
  48. {
  49. perror( "malloc" );
  50. return -1;
  51. }
  52. printf( "Starting %d threads...\n", procs );
  53. for (i = 0; i < procs; i++)
  54. {
  55. if (pthread_create( &thrs[i], NULL, thread_routine,
  56. (void *)(long)i ))
  57. {
  58. perror( "pthread_create" );
  59. procs = i;
  60. break;
  61. }
  62. }
  63. for (i = 0; i < procs; i++)
  64. pthread_join( thrs[i], NULL );
  65. free( thrs );
  66. printf( "After doing all the math, global_int value is: %d\n",global_int );
  67. printf( "Expected value is: %d\n", INC_TO * procs );
  68. return 0;
  69. }

上面代码在 RHEL6.9 中编译:g++ -g -o sync_fetch2 sync_fetch2.c -lpthread 执行结果为:

  1. ./sync_fetch2
  2. Starting 4 threads...
  3. After doing all the math, global_int value is: 4000000
  4. Expected value is: 4000000

如果将上面 thread_routine 函数中的这两句换一下,直接用变量加加,则每次执行都得到不一样的值

  1. global_int++;
  2. // __sync_fetch_and_add( &global_int, 1 );

修改后得到结果如下:

  1. $./sync_fetch2
  2. Starting 4 threads...
  3. After doing all the math, global_int value is: 1428371
  4. Expected value is: 4000000
  5. $ ./sync_fetch2
  6. Starting 4 threads...
  7. After doing all the math, global_int value is: 2479197
  8. 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
https://www.modb.pro/db/102030