# C 语言多线程简单运算不加锁实现

因为 c 语言本事并没有提供原子性操作的函数,而使用多线程库当中的锁机制又会大大影响效率,在经过多方查找,以及 redis 源码当中实现计数器的实现了解到了编译器自带的十二个内置原子性操作函数

# 原子性操作函数:

// 返回更新前的值
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, ...)

此外还有三个非运算相关的操作函数:

  1. __sync_bool_compare_and_swap :原子性地比较变量的值与预期值,如果相等则将新值赋给变量。
  2. __sync_val_compare_and_swap :原子性地比较变量的值与预期值,如果相等则将新值赋给变量,并返回操作之前的值。
  3. __sync_lock_test_and_set :原子性地设置变量的值,并返回操作之前的值,通常用于实现互斥锁

# 使用方法:

以自加为例:

#include <stdio.h>
int main() {
    int counter = 0;
    // 使用__sync_fetch_and_add 原子性地递增 counter 的值
    int previous_value = __sync_fetch_and_add(&counter, 1);
    printf("Previous Value: %d\n", previous_value);
    printf("New Value: %d\n", counter);
    return 0;
}

原理:因为该函数本质还是使用的锁机制,但是在汇编当中可以看到,被加锁的指令极少,仅有两三条指令,不同于标准库当中的互斥锁采用的是直接锁住相关的整片内存

image-20230907151137764

使用互斥锁机制,在四个线程下累加 400000 需要 0.019015s 左右

而在使用编译器自带的原子性操作函数累加 400000 次仅需 0.004416s 左右,提升近五倍左右效率

image-20230907151734351

# 完整测试代码

#include <stdio.h>
#include<thread>
#include<mutex>
#include<sys/time.h>

#define ___TIME_CLOCK_DECLARE \
        double t_start_time, t_end_time;
#define ___TIME_CLOCK_START  {\
                struct timeval t; gettimeofday(&t, 0);\
                t_start_time = t.tv_sec + 1E-6 * t.tv_usec; \
        }
#define ___TIME_CLOCK_STOP { \
                struct timeval t; gettimeofday(&t, 0); \
                t_end_time = t.tv_sec + 1E-6 * t.tv_usec; \
        }
#define ___TIME_CLOCK_TOTAL (t_end_time - t_start_time)
using namespace std;
int sum=0;
int counter = 0;
std::mutex lo;
void test1(){
    for(int i=0;i<100000;i++){
        // lo.lock();
        // sum+=1;
        // lo.unlock();
        __sync_fetch_and_add(&counter, 1);
    }
}
void test2(){
    for(int i=0;i<100000;i++){
        // lo.lock();
        // sum+=1;
        // lo.unlock();
        __sync_fetch_and_add(&counter, 1);
    }
}
void test3(){
    for(int i=0;i<100000;i++){
        // lo.lock();
        // sum+=1;
        // lo.unlock();
        __sync_fetch_and_add(&counter, 1);
    }
}
void test4(){
    for(int i=0;i<100000;i++){
        // lo.lock();
        // sum+=1;
        // lo.unlock();
        __sync_fetch_and_add(&counter, 1);
    }
}
int main() {

    ___TIME_CLOCK_DECLARE
    ___TIME_CLOCK_START
    std::thread p(test1);
    std::thread p2(test2);
    std::thread p3(test3);
    std::thread p4(test4);

    p.join();
    p2.join();
    p3.join();
    p4.join();
    ___TIME_CLOCK_STOP
    printf("sum:%d  time:%lf\n",counter,___TIME_CLOCK_TOTAL);
    return 0;
}

(为了编写代码方便,部分地方采用了 c++ 写法,但是不影响测试结果数据)