博客园  :: 首页  :: 新随笔  :: 管理

3.1.1 原子操作CAS与锁

Posted on 2023-03-09 23:15  wsg_blog  阅读(86)  评论(0编辑  收藏  举报

Linux C/C++服务器

原子操作CAS与锁

1.互斥锁 2.自旋锁 3.读写锁 4.原子操作 5.shmem

多线程同时操作同一个变量问题

多线程执行 idx++,实际运行是转换成三行汇编代码去运行,绝大多数情况下没啥问题,如下图所示

但有一小部分会出现问题,如下

如何解决?
首先想到的就是加锁,再有就是原子操作

多线程中的锁

只允许一个线程对变量进行操作

Global int idx;  //全局定义
lock(mtx);
idx ++;  //多线程
unlock(mtx);

mutex(互斥锁)与spinlock(自旋锁)

mutex和spinlock在应用层写法是一样的,内部原理上有区别,spinlock是线程阻塞的等另一个线程释放锁,mutex是非阻塞的,先去执行别的事情,过一会再回来看下锁是否释放
至于使用哪个,可以做个时间统计来决定

原子操作

什么叫原子操作:能够用一条指令去执行,不可分割的代码段,在自增自减中常用

汇编实现

我们使用汇编自己实现,把编译器实现三条汇编指令的i++改为一条

int inc(int *value, int add) {
	int old;
	__asm__ volatile (
		"lock; xaddl %2, %1;"  //锁cpu到内存的连接总线
		: "=a" (old)
		: "m" (*value), "a" (add)
		: "cc", "memory"
	);
	return old;
}

CAS实现

Compare And Swap 先对比再赋值,如果一致则赋值,如果不一致不赋值
if(A==B){A=C};指令集支持用一条汇编指令实现这个逻辑
c++11中有实现原子操作的方法atomic.compare_exchage_strong(exp, des); //cas
单例模式中创建实例常用:if(instance == NULL) instance = malloc();

int old = *pcount;
while(!__sync_bool_compare_and_swap(pcount, old, old+1)){
  old = *pcount;	//更新
}

//__sync_bool_compare_and_swap 伪代码 
/*bool CAS( int * pAddr, int nExpected, int nNew )
atomically {
	if ( *pAddr == nExpected ) {
		*pAddr = nNew;
		return true;
	}
	return false;
}*/

测试代码

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/time.h>
#include <stdatomic.h>

#define THREAD_SIZE		10

pthread_mutex_t mutex;
pthread_spinlock_t spinlock;

#define TIME_SUB_MS(tv1, tv2)  ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)

int inc(int *value, int add) {
	int old;
	__asm__ volatile (
		"lock; xaddl %2, %1;"
		: "=a" (old)
		: "m" (*value), "a" (add)
		: "cc", "memory"
	);
	return old;
}

// 10 * 100000
void *func (void *arg) {
	int *pcount = (int *)arg;
	int i = 0;
	while (i ++ < 1000000) {
#if 0
		(*pcount)++;
#elif 0  //mutex
		pthread_mutex_lock(&mutex);
		(*pcount)++;
		pthread_mutex_unlock(&mutex);
#elif 0  //spin_lock
		pthread_spin_lock(&spinlock);
		(*pcount)++;
		pthread_spin_unlock(&spinlock);
#elif 0  //cas原子操作
		int old = *pcount;
                while(!__sync_bool_compare_and_swap(pcount, old, old+1)){
                    old = *pcount;	//更新
                }
#else   //汇编原子操作
		inc(pcount, 1);
#endif
		//usleep(1);
	}
}

int main() {
	pthread_t threadid[THREAD_SIZE] = {0};

	pthread_mutex_init(&mutex, NULL);
	pthread_spin_init(&spinlock, PTHREAD_PROCESS_SHARED);
	
	struct timeval tv_start;
	gettimeofday(&tv_start, NULL);

	int i = 0;
	int count = 0;
	for (i = 0;i < THREAD_SIZE;i ++) {
		pthread_create(&threadid[i], NULL, func, &count);
	}
#if 1
	for (i = 0;i < THREAD_SIZE;i ++) {
		pthread_join(threadid[i], NULL); //
	}
#endif

	struct timeval tv_end;
	gettimeofday(&tv_end, NULL);
	int time_used = TIME_SUB_MS(tv_end, tv_start);
	printf("time_used: %d\n", time_used);
#if 0
	// 100w 
	for (i = 0;i < 100;i ++) {
		printf("count --> %d\n", count);
		sleep(1);
	}
#endif
}

多线程接口说明

usleep(0)
字面意思,睡眠0ms; 主要由于线程切换,会有一个让出cpu执行权的动作
pthread_create
pthread_create(&threadid[i], NULL, func, &count);
pthread_create 只是创建一个线程的task_thread结构,并放在一个队列中,由内核去调度,这个接口并不会主动的触发调度机制
pthread_join
pthread_join(threadid[i], NULL);
等待threadid[i]线程的回调函数执行完,主要是针对耗时操作的接口使用