Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3892047
  • 博文数量: 146
  • 博客积分: 3918
  • 博客等级: 少校
  • 技术积分: 8585
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-17 13:52
个人简介

个人微薄: weibo.com/manuscola

文章分类

全部博文(146)

文章存档

2016年(3)

2015年(2)

2014年(5)

2013年(42)

2012年(31)

2011年(58)

2010年(5)

分类: LINUX

2011-11-13 15:21:43

      昨天发了博文后,对并发编程开始有点兴趣,我就看了Paul E. McKenney大师的并行编程的那本书,那本书第四章重点将了多线程并发条件下的计数。有兴趣的同学可以看下,讲的比较详细。我就对大师基于每线程变量的统计计数器做了研究,实现了下。其实大师基本代码都已经敲出来了。我只是实现了一下,就当练练手,同时向大师致敬。

    这个方法原理比较简单,就是每个线程维护自己的计数器,到了统计计数的时候,把每个线程的计数器的值加在一起,就是总计数器的值。当然加的时候要加锁保护。比较特殊的地方是定义了个
  1. int __thread count = 0;
    这个__thread声明的表示每个线程各自维护一个各自的int型变量 count,这就是江湖上传闻已久的TLS, Thread-Local Storage,有兴趣的同学可以学习Ulrich Drepper大牛的ELF handling of Thread-Local Storage。

    这种方案使用场合是计数器自加很频繁,但是查看计数器的值不频繁的场合,因为查看总计数器需要加锁操作,而每个线程自加不需要加锁,每个线程各自维护各自的计数器。这种应用场景很普遍,前文中我需要设计的计数器就是这样。日常维护也许好长时间才看一样相关统计量。
    下面上代码:

  1. #define _GNU_SOURCE

  2. #include<stdio.h>
  3. #include<pthread.h>
  4. #include<stdlib.h>
  5. #include<sys/poll.h>
  6. #include<unistd.h>
  7. #include<time.h>
  8. #include<linux/types.h>

  9. #define NR_THREADS 10

  10. #define COUNT 5000

  11. int __thread count = 0;
  12. int *pcount[NR_THREADS] = {NULL};
  13. int finalcount = 0;


  14. pthread_spinlock_t g_spinlock;

  15. void inc_count()
  16. {
  17.     count++;
  18. }

  19. int read_count()
  20. {
  21.     int t;
  22.     int sum ;

  23.     pthread_spin_lock(&g_spinlock);
  24.     sum = finalcount;
  25.     for(t = 0;t<NR_THREADS;t++)
  26.     {    
  27.          if(pcount[t] != NULL)
  28.             sum += *pcount[t];
  29.     }
  30.     pthread_spin_unlock(&g_spinlock);
  31.     return sum;
  32. }


  33. typedef struct
  34. {
  35.    int nr;
  36.    pthread_t tid;
  37. }Thread;

  38. Thread Threads[NR_THREADS];

  39. void count_register_thread(int idx)
  40. {
  41.     pthread_spin_lock(&g_spinlock);
  42.     pcount[idx] = &count;
  43.     pthread_spin_unlock(&g_spinlock);
  44. }

  45. void count_unregister_thread(int idx)
  46. {
  47.     pthread_spin_lock(&g_spinlock);
  48.     finalcount += count;
  49.     pcount[idx] = NULL;
  50.     pthread_spin_unlock(&g_spinlock);
  51. }

  52. void *thr_function(void* arg)
  53. {
  54.    int nr = *(int*)arg;
  55.    fprintf(stderr,"the %d thread init successful\n",nr);
  56.    struct timespec delay = {0};
  57.    delay.tv_sec = 0;
  58.    delay.tv_nsec = 1000000;
  59.    struct timeval tv_begin,tv_end;
  60.    __u64 interval = 0;
  61.     
  62.    count_register_thread(nr);

  63.    int i,j;
  64.    
  65.    gettimeofday(&tv_begin,NULL);
  66.    for(i = 0;i<COUNT;i++)
  67.    {
  68.      inc_count();
  69.      nanosleep(&delay,NULL);
  70.    }
  71.    gettimeofday(&tv_end,NULL);

  72.    interval = (tv_end.tv_sec -tv_begin.tv_sec)*1000000               + (tv_end.tv_usec-tv_begin.tv_usec);
  73.    fprintf(stderr,"the thread %d cost %llu us\n",                        nr,interval);
  74.    count_unregister_thread(nr);
  75.    fprintf(stderr,"the %d thread will exit\n",nr);
  76.    return NULL;
  77. }

  78. int main()
  79. {
  80.     int i;

  81.     pthread_spin_init(&g_spinlock,0);


  82.     for(i= 0;i<NR_THREADS;i++)
  83.     {
  84.        Threads[i].nr = i;
  85.        if(pthread_create(&Threads[i].tid,NULL,                                  thr_function,&Threads[i].nr))
  86.        {
  87.             fprintf(stderr,"error happened when create                             thread %d\n",i);
  88.              return 0;
  89.        }
  90.     }

  91.     for(i= 0;i<10;i++)
  92.     {

  93.         poll(NULL,0,500);
  94.         fprintf(stderr,"MASTER PROC: the finalcount is                         %d now \n",read_count());
  95.     }

  96.     for(i = 0;i<NR_THREADS;i++)
  97.     {
  98.         if(pthread_join(Threads[i].tid,NULL))
  99.         {
  100.               fprintf(stderr,"error happened when join                               the thread %d\n",i);
  101.               return ;
  102.         }
  103.     }

  104.     fprintf(stderr,"MASTER PROC: at last finalcount is                     %d\n ",finalcount);
  105.     return 0;
  106. }
运行结果如下:
  1. the 1 thread init successful
  2. the 0 thread init successful
  3. the 3 thread init successful
  4. the 4 thread init successful
  5. the 2 thread init successful
  6. the 5 thread init successful
  7. the 6 thread init successful
  8. the 7 thread init successful
  9. the 8 thread init successful
  10. the 9 thread init successful
  11. MASTER PROC: the finalcount is 4634 now
  12. MASTER PROC: the finalcount is 9255 now
  13. MASTER PROC: the finalcount is 13880 now
  14. MASTER PROC: the finalcount is 18482 now
  15. MASTER PROC: the finalcount is 23129 now
  16. MASTER PROC: the finalcount is 27761 now
  17. MASTER PROC: the finalcount is 32395 now
  18. MASTER PROC: the finalcount is 36992 now
  19. MASTER PROC: the finalcount is 41644 now
  20. MASTER PROC: the finalcount is 46282 now
  21. the thread 1 cost 5401657 us
  22. the thread 4 cost 5401638 us
  23. the 4 thread will exit
  24. the 1 thread will exit
  25. the thread 8 cost 5403640 us
  26. the 8 thread will exit
  27. the thread 7 cost 5404796 us
  28. the 7 thread will exit
  29. the thread 6 cost 5405765 us
  30. the 6 thread will exit
  31. the thread 2 cost 5405876 us
  32. the 2 thread will exit
  33. the thread 3 cost 5406989 us
  34. the 3 thread will exit
  35. the thread 0 cost 5410352 us
  36. the 0 thread will exit
  37. the thread 9 cost 5411182 us
  38. the 9 thread will exit
  39. the thread 5 cost 5412392 us
  40. the 5 thread will exit
  41. MASTER PROC: at last finalcount is 50000
    通过结果可以看出以下几点:
1 总计数器是精确的,线程退出后,finalcount的值和预想值一致可以看出。
2 nanosleep 延迟并不精确。我理论运行时间是5秒,但是实际运行时间都是5.4秒。



参考文献:
1 Paul E. McKenney  Is Parallel Programming Hard, And, If So, What Can You Do About It?
2 Ulrich Drepper    ELF handling of Thread-Local Storage。
阅读(4730) | 评论(4) | 转发(3) |
给主人留下些什么吧!~~

Bean_lee2011-12-03 09:38:12

Heartwork: 这种程度的代码主线程和其他线程间contention太多了。而且主线程加锁期间其他线程还是可以对count计数的。(因为inc_count没有加锁)

简单描述一下可以改进的地.....
我这个计数器查看结果时就是大致一致的计数器。兄弟这段代码妙阿。新生成一个计数用的数组,用来计数,老的数组用来看结果,减少了冲突。

但是新生成的计数器数组是不是再次从0 开始计数了,我看你的代码是这个意思。

感谢前辈指教。

Heartwork2011-11-29 00:40:22

nanosleep的问题很好解释,看这里……

    The nanosleep() function shall cause the current thread to be suspended from execution until either the time interval specified by the rqtp argument has elapsed or a signal is delivered to the calling thread, and its action is to invoke a signal-catching function or to terminate the process. The suspension time may be longer than requested because the argument value is rounded up to an integer multiple of the sleep resolution or because of the

Heartwork2011-11-29 00:19:31

这种程度的代码主线程和其他线程间contention太多了。而且主线程加锁期间其他线程还是可以对count计数的。(因为inc_count没有加锁)

简单描述一下可以改进的地方:
// 不使用count,直接对pcount[nr]计数。
int *count = malloc(sizeof(int) * NR_THREADS);
// 使用读写锁保护pcount,主线程调用read_count时使用写锁,各个线程调用inc_count时使用读锁。
pthread_rwlock_t lock;

int read_count()
{
    int t;

    // count的副本,malloc后的数组可能需要重新赋初值0。
    int *temp = malloc(sizeof(int*) * NR_THREADS);
&

GFree_Wind2011-11-14 12:27:45

这个在多核的程序中的应用很广泛。。。