Chinaunix首页 | 论坛 | 博客
  • 博客访问: 246829
  • 博文数量: 35
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 273
  • 用 户 组: 普通用户
  • 注册时间: 2014-11-27 23:34
个人简介

To follow the path,look to the master,follow the master.

文章分类

全部博文(35)

文章存档

2019年(1)

2018年(1)

2017年(1)

2016年(8)

2015年(24)

分类: LINUX

2015-04-22 15:17:55

原文地址:线程锁和自旋锁的比较 作者:Bean_lee

     最近从事多线程相关的编程,对于多线程的性能比较关心,所以去网上找了一些资料。看到了并行实验室的冠诚前辈的博文 学习到了很多,下面是我的学习笔记。光荣属于前辈。

    线程锁调用API如下:
  1.       pthread_mutex_lock(&mutex);
  2.       pthread_mutex_unlock(&mutex);
   自旋锁调用的API 如下:
  1.      pthread_spin_lock(&spinlock);
  2.      pthread_spin_unlock(&spinlock);
    所谓线程锁,是指如果线程没有抢到线程锁,那么线程就会被阻塞,线程所在的CPU会发生进程调度,选择其他进程执行。所以可以想见,如果线程锁竞争的非常激烈(如100个线程争一把线程锁),那么,上下文切换会非常的多。下面我们的实验会证明这一点。
    而自旋锁则不同,它属于busy-waiting类型的锁,线程竞争自旋锁,如果竞争不到,线程会不停的忙等待,不停的重试锁请求。如果长时间请求不到自旋锁,自旋锁看起来就像死循环一样。从自旋锁的特点来看,自旋锁只适合与竞争不太激烈(即并发争锁的线程个数不多,),并且临界区不大的情况。下面我们的实验也会证明这一点。

    下面是我的测试代码,little和big是临界区要执行的代码。故名思议,little是临界区粒度很小,big是临界区很大。通过宏来控制选择自旋锁还是线程锁。
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<pthread.h>

  4. #define USE_SPINLOCK

  5. #ifdef USE_SPINLOCK
  6. pthread_spinlock_t spinlock;
  7. #else
  8. pthread_mutex_t mutex;
  9. #endif


  10. #define NR_THREAD 2
  11. #define MILLION 1000000L
  12. #define TIMES 100000
  13. #define EXEC_TIMES 1000000

  14. unsigned long long counter = 0;

  15. inline int little()
  16. {
  17.     counter++;
  18. }

  19. inline int big()
  20. {
  21.     int j;
  22.     for(j = 0;j<TIMES;j++)
  23.     {
  24.          counter++;
  25.     }
  26. }

  27. void * worker(void* arg)
  28. {
  29.    int i;
  30.      for(i = 0;i<EXEC_TIMES;i++)
  31.      {
  32.      #ifdef USE_SPINLOCK
  33.              pthread_spin_lock(&spinlock);
  34.     #else
  35.              pthread_mutex_lock(&mutex);
  36.     #endif

  37.              little();
  38.              //big();

  39.      #ifdef USE_SPINLOCK
  40.              pthread_spin_unlock(&spinlock);
  41.     #else
  42.              pthread_mutex_unlock(&mutex);
  43.     #endif

  44.      }

  45.      return NULL;
  46. }
  47. int main()
  48. {
  49.   int i;
  50.     struct timeval tv_start,tv_end;
  51.     unsigned long long interval = 0;
  52. #ifdef USE_SPINLOCK
  53.     pthread_spin_init(&spinlock,0);
  54. #else
  55.     pthread_mutex_init(&mutex,NULL);
  56. #endif
  57.   pthread_t Tid[NR_THREAD];
  58.     
  59.     gettimeofday(&tv_start,NULL);
  60.     for(i = 0;i<NR_THREAD;i++)
  61.     {
  62.          if(pthread_create(&Tid[i],NULL,worker,NULL) != 0)
  63.          {
  64.                fprintf(stderr,"pthread create failed 
  65.                                when i = %d\n",i);
  66.                return -1;
  67.          }
  68.     }

  69.     for(i = 0;i<NR_THREAD;i++)
  70.     {
  71.             if(pthread_join(Tid[i],NULL))
  72.             {
  73.                   fprintf(stderr,"pthread join failed 
  74.                                   when i = %d\n",i);
  75.                   return -2;
  76.             }
  77.     }
  78.     
  79.     gettimeofday(&tv_end,NULL);
  80.     interval = MILLION*(tv_end.tv_sec - tv_start.tv_sec )
  81.                      + (tv_end.tv_usec - tv_start.tv_usec);

  82. #ifdef USE_SPINLOCK
  83.     fprintf(stderr,"thread num %d spinlock version 
  84.                     cost time %llu\n",NR_THREAD,interval);
  85. #else
  86.     fprintf(stderr,"thread num %d mutex version 
  87.                     cost time %llu\n",NR_THREAD,interval);
  88. #endif

  89.     return 0;
  90. }
 1 临界区小,线程个数为2 
  1. root@libin:~/program/C/thread/thread_lock_cmp# time ./mutex_2_comp
  2. thread num 2 mutex version cost time 193155

  3. real    0m0.195s
  4. user    0m0.208s
  5. sys    0m0.172s
  6. root@libin:~/program/C/thread/thread_lock_cmp# time ./spinlock_2_comp
  7. thread num 2 spinlock version cost time 179761

  8. real    0m0.181s
  9. user    0m0.360s
  10. sys    0m0.000s
性能上看差不多,这是由于线程数比较小,竞争不激烈。关注下sys 时间,mutex锁版本的时间大,因为它会存在争不到锁而调用system wait情况。

2 临界区小,线程个数为10
  1. root@libin:~/program/C/thread/thread_lock_cmp# time ./mutex_10_comp
  2. thread num 10 mutex version cost time 1456112
  3. real 0m1.458s
  4. user 0m1.840s
  5. sys 0m3.808s
  6. root@libin:~/program/C/thread/thread_lock_cmp# time ./spinlock_10_comp
  7. thread num 10 spinlock version cost time 2425690
  8. real 0m2.427s
  9. user 0m9.577s
  10. sys 0m0.016s
  11. root@libin:~/program/C/thread/thread_lock_cmp#
看下10个线程的情况,自旋锁性能已经明显不如线程锁了。因为竞争变得激烈了。我使用systemtap观察了进程调度的频繁程度,每秒统计一次上下文切换的次数
  1. root@libin:~/program/systemtap# cat sched.stp
  2. global cnt;
  3. probe scheduler.cpu_on {cnt<<<1;}
  4. probe timer.s(1){printf("%d\n", @count(cnt)); delete cnt;}
  5. probe timer.s(40){exit();}
  6. root@libin:~/program/systemtap#

线程锁上下文切换的情况:
  1. 2393
  2. 2275
  3. 2156
  4. 122098
  5. 72827
  6. 2741
  7. 4760
  8. 3159

看到中间有两个比较大的值,就是因为我执行了mutex版本的程序,而程序执行时间只有1.5秒左右,所以只有两个比较大的值。这就证明了mutex锁存在激烈竞争的情况下,会出现大量的上下文切换。

自旋锁版本执行期间,上下文切换没有明显变化,表明自旋锁不会引发上下文切换。它原地死循环。

3 临界区小,竞争特别激烈 100个线程。
先说mutex锁的情况:
  1. root@libin:~/program/C/thread/thread_lock_cmp# time ./mutex_100_comp
  2. thread num 100 mutex version cost time 15101059

  3. real    0m15.103s
  4. user    0m18.337s
  5. sys    0m40.827s
执行systemtap脚本的输出:
  1. 3567
  2. 2245
  3. 2291
  4. 82863
  5. 122166
  6. 110381
  7. 126612
  8. 126960
  9. 124175
  10. 126085
  11. 126417
  12. 120905
  13. 119271
  14. 120717
  15. 125181
  16. 124713
  17. 126694
  18. 125177
  19. 51845
  20. 4633
  21. 2633
就像10个线程的情况一样,在执行mutex版本期间,发生了大量的上下文切换。
top的输出如下:
  1. top - 12:46:38 up 2:27, 4 users, load average: 16.72, 7.52, 3.88
  2. Tasks: 223 total, 3 running, 220 sleeping, 0 stopped, 0 zombie
  3. Cpu0 : 35.0%us, 64.7%sy, 0.0%ni, 0.3%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
  4. Cpu1 : 32.4%us, 67.0%sy, 0.3%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st
  5. Cpu2 : 35.0%us, 65.0%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
  6. Cpu3 : 34.8%us, 64.5%sy, 0.0%ni, 0.3%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st
  7. Mem: 1985648k total, 1441328k used, 544320k free, 110548k buffers
  8. Swap: 1951736k total, 0k used, 1951736k free, 521980k cached

  9.   PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
  10.  5774 root 20 0 802m 860 368 S 392 0.0 0:39.27 mutex_100_comp
可以看出,系统时间占60%以上,这是因为有进程调度。

下面看自旋锁,自旋锁就比较悲惨了,我起来自旋锁版本后,先去泡了壶茶,太慢了。
  1. root@libin:~/program/C/thread/thread_lock_cmp# time ./spinlock_100_comp
  2. thread num 100 spinlock version cost time 233026239

  3. real    3m53.028s
  4. user    15m18.985s
  5. sys    0m1.712s
   上下文调度的情况我就不贴了,没有超3000次/s的。
   贴下top的情况
  1. top - 12:49:45 up 2:30, 4 users, load average: 45.98, 15.50, 7.02
  2. Tasks: 230 total, 1 running, 229 sleeping, 0 stopped, 0 zombie
  3. Cpu0 :100.0%us, 0.0%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
  4. Cpu1 : 99.4%us, 0.0%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.6%si, 0.0%st
  5. Cpu2 :100.0%us, 0.0%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
  6. Cpu3 : 98.4%us, 1.3%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st
  7. Mem: 1985648k total, 1471804k used, 513844k free, 111164k buffers
  8. Swap: 1951736k total, 0k used, 1951736k free, 523212k cached

  9.   PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
  10.  5875 root 20 0 802m 860 368 S 395 0.0 2:24.78 spinlock_100_co
程序执行期间,CPU被浪费,我执行其他的任务,电脑特别的卡,卡的不能忍受。

临界区大的情况我就不继续写了。有兴趣的同学可以自己测试一下:

结论:
1 自旋锁适用于竞争不激烈,线程数较少,并且临界区小的情况。
2 线程锁竞争激烈的情况下,引发大量的上下文切换。所以由于竞争的存在,并不是线程愈多,效率越高。
3 保险情况下使用线程锁,因为,极端情况下,自旋锁不停的自旋,浪费CPU,影响效率。


参考文献:

Pthreads并行编程之spin lock与mutex性能对比分析
latencytop深度了解你的Linux系统的延迟
3 UNIX系统编程

阅读(1473) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~