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]线程的回调函数执行完,主要是针对耗时操作的接口使用