当前位置:   article > 正文

volatile与多线程_volatile c++ 多线程

volatile c++ 多线程

目录

C/C++中volatile的传统用途

保证变量的读写操作直接作用于内存

阻止编译器打乱变量之间的读写顺序。

阻止编译器对变量做激进的优化。比如直接去掉某变量相关的指令。

volatile在多线程中的误用

非标准的volatile

总结


 

C/C++中volatile的传统用途

保证变量的读写操作直接作用于内存

即每次读的时候都直接去该变量的内存地址中读,每次写的时候都直接写到该变量的内存地址中去。 看下面的例子,如果没加volatile的话,下面这段代码中变量flag的读操作可能会被编译器优化掉,但如果加了volatile则不会。

  1. bool flag = false;
  2. void fun()
  3. {
  4. while(!flag)
  5. {
  6. // do something
  7. }
  8. }
  9. // flag未加volatile 被优化后相当于下面这样
  10. bool flag = false;
  11. void func()
  12. {
  13. if (!flag) // read only once
  14. {
  15. while(true)
  16. {
  17. // do something
  18. }
  19. }
  20. }

 

阻止编译器打乱变量之间的读写顺序。

注意:只能够保证volatile变量之间的操作顺序,不保证volatile与非volatile变量之间的操作顺序不被编译器改变。 第一个例子,两个变量都没加volatile, 他们的操作顺序被编译器打乱。

第二个例子,只有一个变量是volatile, 另一个不是。 它们的操作顺序还是被改变。

第三个例子,两变量都是volatile, 它们的操作顺序未被改变。

注意: 虽然volatile变量之间的操作顺序不会被编译器打乱,但是还是有可能被CPU打乱, 具体要看CPU的内存模型。这也是volatile不能用于线程同步的原因。

 

阻止编译器对变量做激进的优化。比如直接去掉某变量相关的指令。

  1. int func()
  2. {
  3. int x = 1;// 如果未加volatile的话,x, y两变量会直接被优化掉
  4. int y = 2;
  5. int a = x + y;
  6. printf("a = %d", a);
  7. }

 

volatile在多线程中的误用

因为volatile能够保证变量的读写操作直接作用于内存。所以很多人就想当然的认为可以用volatile的这种特性 来对两线程进行的同步。比如一个经典的错误用法是:用一个volatile flag来同步两个线程。一个线程准备好 数据之后设置flag的值,另一个线程则不停的check flag 的值,根据flag的值来判断数据是否可读。听起来好像 没什么问题,但是大家想一想,万一其中一个线程内的指令被打乱使得在数据还没准备好就设置了flag的值, 这时另一个线程就会读到一些没有初始化的数据,从而导致意想不到的错误。 看下面这个例子:

  1. volatile int flag = 0;
  2. volatile int data;
  3. void Write() // thread one
  4. {
  5. data = 2;
  6. flag = 1;
  7. }
  8. void Read() // thread two
  9. {
  10. while(!flag)
  11. {
  12. printf("data = %d\n", data);
  13. }
  14. }

上面这段代码在X86上没问题,在其他平台上则可能会出现错误。之所以错误是因为 函数write里面的操作data = 2;flag = 1;的执行顺序可以被CPU打乱。注意,我说的是可能被打乱, 至于到底会不会被打乱得看CPU的内存模型。在X86下面是不会被打乱的,因为在X86里面不容 许写写乱序,只容许写读乱序。但是在PowerPC下则会乱序。 下面这张表格记录了各个CPU中可能会被乱序的操作。从表中不难看出在X86下,不容许写写乱序。 注意:X86_64 属于AMD64,也不容许写写乱序。

 

非标准的volatile

前面讲了C/C++中volatile的作用, 传统的volatile对指令的执行顺序无法保证。但是Visual C++和Java里面 对传统的volatile功能进行了拓展。在Visual C++里面,volatile变量的读操作具有Acquire语义而写操作具有 Release语义。Acquire语义是指:紧跟在volatile变量的读操作之后的所有操作必须等到volatile变量的读操作 执行完之后才开始执行,绝不容许将其提到volatile的读操作之前执行。Release语义是指:volatile变量的 写操作之前的所有操作必须在volatile变量的写操作之前执行,绝不容许提到volatile变量的写操作之后执行。 正因为如此,所以下面这段代码在visual C++ 中也是安全的。在write 线程中,是对两个volatile变量进行写操作, 因为写操作具有Acquire 语义所以不能乱序,而在Read线程中是对两个volatile变量进行读操作,因为读操作具有 Relese语义,所以也不能乱序。因此,下面这段代码在visual C++ 中也是安全的。

  1. volatile int flag = 0;
  2. volatile int data;
  3. void Write()
  4. {
  5. data = 2;
  6. flag = 1;
  7. }
  8. void Read()
  9. {
  10. while(!flag)
  11. {
  12. printf("data = %d\n", data);
  13. }
  14. }

 

总结

以上讲到了C/C++中volatile的三种基本用法以及volatile在多线程中的误用。 还顺便提到了Visual C++ 和Java中对volatile传统功能的拓展。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/266079
推荐阅读