cas无锁编程介绍
gcc atomic文档介绍
gcc文档的5.44 Built-in functions for atomic memory access介绍了一组原子操作,其中有一组compare_and_set函数可以用来实现无锁编程:
bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)
type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)
These builtins perform an atomic compare and swap. That is, if the current value of *ptr is oldval, then write newval into *ptr.
The “bool” version returns true if the comparison is successful and newval was written. The “val” version returns the contents of *ptr before the operation.
cas无锁编程一般框架
long long gnum=1000;
////////////////////functions////////////////////
long long val_check_change_work_if_atomic(long long *pval,long long offset,bool (*is_check_ok)(long long cul_val),int (*work_func)(long long val)){
long long now_val;
bool atomic;
while(is_check_ok(now_val=*pval)){
atomic=__sync_bool_compare_and_swap(pval,now_val,now_val+offset);
if(atomic){
//long long next_val=now_val+offset;
work_func(now_val);
}
}
return now_val;
}
inline bool work_thread_check_func(long long val){
return val>0;
}
int work_thread_work_func(long long val){
//printf("%d work on %lld\n",getpid(),val);
return 0;
}
void* work_thread(void* arg){
long now_val;
while(work_thread_check_func(now_val=val_check_change_work_if_atomic(
&gnum
,-1
,work_thread_check_func
,work_thread_work_func
)
)
){
}
return NULL;
}
- 关键是
val_check_change_work_if_atomic
这个函数,这个函数看懂了,cas无锁编程也看懂了。 - 为了抽象,将判断和工作过程当做两个函数指针参数
work_thread_check_func
work_thread_work_func
。不过这样函数调用有性能损耗,实际上弄熟了的话,可以都放在work_thread
这一个函数里面。也注意下work_thread
也调用了work_thread_check_func
。
为了测试cas无锁编程的性能,写了三个程序进行测试。
性能测试
使用gcc cas无锁的并发保护版本:test_gcc_atomic
注意,lhf.h
里面定义了print_error
函数,可以直接替换为printf函数后exit来去掉这个头文件。另外两个版本只是work_thread
实现不同,所以只给出work_thread
函数。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "lhf.h"
////////////////////variables////////////////////
pthread_mutex_t gmutex;
long long gnum=1000;
////////////////////functions////////////////////
void* work_thread(void* arg){
long long now_val;
bool atomic;
while((now_val=gnum)>0){
atomic=__sync_bool_compare_and_swap(&gnum,now_val,now_val-1);
if(atomic){
//ok,work here
//long long next_val=now_val-1
}
}
return NULL;
}
////////////////////main////////////////////
int main(int argc,char* argv[]){
int thread_num=100;
int re;
clock_t begin,end,use;
if(argc>1){
thread_num=atoi(argv[1]);
}
if(argc>2){
gnum=strtoll(argv[2],NULL,10);
}
begin=clock();
pthread_t *threads=(pthread_t*)calloc(thread_num,sizeof(pthread_t));
pthread_mutex_init(&gmutex,NULL);
for(int i=0;i<thread_num;i++){
if(pthread_create(threads+i,NULL,work_thread,(void *)(long)i)<0){
print_error(1,"pthread_create",strerror(errno));
}
}
void *exist_status;
for(int i=0;i<thread_num;i++){
if((re=pthread_join(threads[i],&exist_status))<0){
print_error(1,"pthread_create",strerror(errno));
}
}
pthread_mutex_destroy(&gmutex);
free(threads);
end=clock();
use=end-begin;
printf("in main:%lld use %ld second(%ld)\n",gnum,use/CLOCKS_PER_SEC,use);
return 0;
}
使用pthread_mutex_lock
的并发保护版本:test_gcc_atomic_use_lock
void* work_thread(void* arg){
int num_cache=gnum;
while(num_cache){
pthread_mutex_lock(&gmutex);
if(gnum>0){
gnum--;
}
num_cache=gnum;
pthread_mutex_unlock(&gmutex);
}
return NULL;
}
无并发保护的版本:test_gcc_atomic_no_protect
void* work_thread(void* arg){
while(gnum>0){
gnum--;
}
return NULL;
}
性能测试
[lhf@qcloud bld]$ test_gcc_atomic_no_protect 10 1000000000
in main:-9 use 2 second(2390000)
[lhf@qcloud bld]$ test_gcc_atomic 10 1000000000
in main:0 use 14 second(14280000)
[lhf@qcloud bld]$ atomic_basic 10 1000000000
in main:0 use 16 second(16840000)
[lhf@qcloud bld]$ test_gcc_atomic_use_lock 10 1000000000
in main:0 use 21 second(21880000)
- 首先是无并发保护的版本,结果错误,为-9,但是用时最短2秒.
- 然后是cas并发保护的版本,结果正确,用时14秒(换为函数调用的atomic_basic是16秒)
- 最后是
pthread_mutex_lock
并发保护的版本,结果正确,用时21秒 - 1GHz的单核cpu上测试,如果多核的话,效果应该更好。
可以看到用锁的版本是用cas版本时间的1.5倍,这其中也包括线程创建和结束时间,如果测试的数据更大,cas版本应该更好。