Linux环境多线程编程基础设施

系统 Linux
本文介绍多线程环境下并行编程的基础设施。

本文介绍多线程环境下并行编程的基础设施。主要包括:

  • volatile
  • __thread
  • Memory Barrier
  • __sync_synchronize

volatile

编译器有时候为了优化性能,会将一些变量的值缓存到寄存器中,因此如果编译器发现该变量的值没有改变的话,将从寄存器里读出该值,这样可以避免内存访问。

但是这种做法有时候会有问题。如果该变量确实(以某种很难检测的方式)被修改呢?那岂不是读到错的值?是的。在多线程情况下,问题更为突出:当某个线程对一个内存单元进行修改后,其他线程如果从寄存器里读取该变量可能读到老值,未更新的值,错误的值,不新鲜的值。

如何防止这样错误的“优化”?方法就是给变量加上volatile修饰。

  1. volatile int i=10;//用volatile修饰变量i 
  2.  
  3. ......//something happened 
  4.  
  5. int b = i;//强制从内存中读取实时的i的值  

OK,毕竟volatile不是完美的,它也在某种程度上限制了优化。有时候是不是有这样的需求:我要你立即实时读取数据的时候,你就访问内存,别优化;否则,你该优化还是优化你的。能做到吗?

不加volatile修饰,那么就做不到前面一点。加了volatile,后面这一方面就无从谈起,怎么办?伤脑筋。

其实我们可以这样:

  1. int i = 2; //变量i还是不用加volatile修饰 
  2.  
  3. #define ACCESS_ONCE(x) (* (volatile typeof(x) *) &(x))  

需要实时读取i的值时候,就调用ACCESS_ONCE(i),否则直接使用i即可。

这个技巧,我是从《Is parallel programming hard?》上学到的。

听起来都很好?然而险象环生:volatile常被误用,很多人往往不知道或者忽略它的两个特点:在C/C++语言里,volatile不保证原子性;使用volatile不应该对它有任何Memory Barrier的期待。

第一点比较好理解,对于第二点,我们来看一个很经典的例子:

  1. volatile int is_ready = 0; 
  2.  
  3. char message[123]; 
  4.  
  5. void thread_A 
  6.  
  7.  
  8.   while(is_ready == 0) 
  9.  
  10.   { 
  11.  
  12.   } 
  13.  
  14.   //use message; 
  15.  
  16.  
  17. void thread_B 
  18.  
  19.  
  20.   strcpy(message,"everything seems ok"); 
  21.  
  22.   is_ready = 1; 
  23.  
  24.  

线程B中,虽然is_ready有volatile修饰,但是这里的volatile不提供任何Memory Barrier,因此12行和13行可能被乱序执行,is_ready = 1被执行,而message还未被正确设置,导致线程A读到错误的值。

这意味着,在多线程中使用volatile需要非常谨慎、小心。

__thread

__thread是gcc内置的用于多线程编程的基础设施。用__thread修饰的变量,每个线程都拥有一份实体,相互独立,互不干扰。举个例子:

  1. #include 
  2.  
  3. #include 
  4.  
  5. #include 
  6.  
  7. using namespace std; 
  8.  
  9. __thread int i = 1; 
  10.  
  11. void* thread1(void* arg); 
  12.  
  13. void* thread2(void* arg); 
  14.  
  15. int main() 
  16.  
  17.  
  18.   pthread_t pthread1; 
  19.  
  20.   pthread_t pthread2; 
  21.  
  22.   pthread_create(&pthread1, NULL, thread1, NULL); 
  23.  
  24.   pthread_create(&pthread2, NULL, thread2, NULL); 
  25.  
  26.   pthread_join(pthread1, NULL); 
  27.  
  28.   pthread_join(pthread2, NULL); 
  29.  
  30.   return 0; 
  31.  
  32.  
  33. void* thread1(void* arg) 
  34.  
  35.  
  36.   coutiendl;//输出 2   
  37.  
  38.   return NULL
  39.  
  40.  
  41. void* thread2(void* arg) 
  42.  
  43.  
  44.   sleep(1); //等待thread1完成更新 
  45.  
  46.   coutiendl;//输出 2,而不是3 
  47.  
  48.   return NULL
  49.  
  50.  

需要注意的是:

1,__thread可以修饰全局变量、函数的静态变量,但是无法修饰函数的局部变量。

2,被__thread修饰的变量只能在编译期初始化,且只能通过常量表达式来初始化。

Memory Barrier

为了优化,现代编译器和CPU可能会乱序执行指令。例如:

  1. int a = 1; 
  2.  
  3. int b = 2; 
  4.  
  5. a = b + 3; 
  6.  
  7. b = 10;  

CPU乱序执行后,第4行语句和第5行语句的执行顺序可能变为先b=10然后再a=b+3

有些人可能会说,那结果不就不对了吗?b为10,a为13?可是正确结果应该是a为5啊。

哦,这里说的是语句的执行,对应的汇编指令不是简单的mov b,10和mov b,a+3。

生成的汇编代码可能是:

  1. movl    b(%rip), %eax ; 将b的值暂存入%eax 
  2.  
  3. movl    $10, b(%rip) ; b = 10 
  4.  
  5. addl    $3, %eax ; %eax加3 
  6.  
  7. movl    %eax, a(%rip) ; 将%eax也就是b+3的值写入a,即 a = b + 3  

这并不奇怪,为了优化性能,有时候确实可以这么做。但是在多线程并行编程中,有时候乱序就会出问题。

一个最典型的例子是用锁保护临界区。如果临界区的代码被拉到加锁前或者释放锁之后执行,那么将导致不明确的结果,往往让人不开心的结果。

还有,比如随意将读数据和写数据乱序,那么本来是先读后写,变成先写后读就导致后面读到了脏的数据。因此,Memory Barrier就是用来防止乱序执行的。具体说来,Memory Barrier包括三种:

1,acquire barrier。acquire barrier之后的指令不能也不会被拉到该acquire barrier之前执行。

2,release barrier。release barrier之前的指令不能也不会被拉到该release barrier之后执行。

3,full barrier。以上两种的合集。

所以,很容易知道,加锁,也就是lock对应acquire barrier;释放锁,也就是unlock对应release barrier。哦,那么full barrier呢?

__sync_synchronize

__sync_synchronize就是一种full barrier。 

责任编辑:庞桂玉 来源: 程序源
相关推荐

2009-12-18 17:14:25

惠普基础架构

2009-12-22 13:59:59

惠普基础设施运营

2022-02-10 11:54:34

即时基础设施基础设施数字化转型

2009-03-12 10:52:43

Java线程多线程

2023-07-17 18:43:26

测试基础设施开发

2020-04-09 10:57:12

超融合基础设施服务器超融合

2011-06-13 10:41:17

JAVA

2017-09-16 17:28:55

基础设施代码持续交付

2022-04-06 15:04:28

网络安全基础设施

2023-08-04 16:32:18

2023-06-16 15:53:55

DevOps基础设施

2021-05-08 13:13:55

智能设施漏洞攻击

2020-02-24 11:08:27

云计算网络攻击数据

2020-04-28 10:21:58

基础设施硬件远程工作

2017-06-09 15:25:23

IT设施数据中心融合

2015-12-07 09:39:53

光纤数据中心

2013-08-01 09:12:41

企业基础设施虚拟化网络设备

2012-02-08 13:48:32

存储公有云

2015-05-27 09:03:46

IT基础设施IT基础设施监控

2021-08-11 09:18:17

能源物联网应用物联网
点赞
收藏

51CTO技术栈公众号