gcc cas无锁编程和性能测试

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版本应该更好。

猜你喜欢

转载自blog.csdn.net/u013319359/article/details/81024749
gcc
今日推荐