mur*_*att 27 c++ multithreading multicore volatile
由于我对这个问题的回答,我开始阅读关键字volatile以及关于它的共识.我看到有很多关于它的信息,一些旧的现在看起来是错的,而且很多新的说它在多线程编程中几乎没有位置.因此,我想澄清具体的用法(在SO上找不到确切的答案).
我还想指出我一般都理解编写多线程代码的要求,以及为什么volatile不解决问题.不过,我看到代码volatile用于我工作的代码库中的代码.此外,这是我使用volatile关键字的唯一情况,因为所有其他共享资源都已正确同步.
假设我们有一个类:
class SomeWorker
{
public:
    SomeWorker() : isRunning_(false) {}
    void start() { isRunning_ = true; /* spawns thread and calls run */ }
    void stop() { isRunning_ = false; }
private:
    void run()
    {
        while (isRunning_)
        {
            // do something
        }
    }
    volatile bool isRunning_;
};
为简单起见,有些事情被遗漏了,但重要的是创建了一个对象,它在新生成的线程中执行某些操作,检查a(volatile)布尔值以了解它是否应该停止.只要希望工作者停止,就会从另一个线程设置此布尔值.
我的理解是,volatile在这种特定情况下使用的原因只是为了避免任何将其缓存在循环寄存器中的优化.因此,导致无限循环.没有必要正确地同步事物,因为工作线程最终将获得新值?
我想知道这是否被认为是完全错误的,如果正确的方法是使用同步变量?编译器/架构/核心之间有区别吗?也许这只是一个值得避免的草率方法?
如果有人澄清这一点,我会很高兴.谢谢!
编辑
我有兴趣看到(在代码中)你如何选择解决这个问题.
Mar*_* Ba 10
volatile 可用于此类目的.但是,这是Microsoft对标准C++的扩展:
微软特定
声明为volatile的对象是(...)
- 对volatile对象的写入(volatile write)具有Release语义; (......)
- 读取volatile对象(volatile read)具有Acquire语义; (......)
这允许volatile对象用于多线程应用程序中的内存锁定和释放.(emph.补充)
也就是说,据我所知,当您使用Visual C++编译器时,a volatile bool用于大多数实际目的atomic<bool>.
应该注意的是,较新的 VS版本添加了一个控制此行为的/ volatile开关,因此仅在/volatile:ms处于活动状态时才会保留.
您不需要同步变量,而是需要原子变量.幸运的是,你可以使用std::atomic<bool>.
关键问题是,如果多个线程同时访问同一个内存,那么除非访问是原子的,否则整个程序将不再处于定义良好的状态.也许你很幸运有一个bool,它可能在任何情况下都会以原子方式进行更新,但是在进攻中确定你做得对的唯一方法就是使用原子变量.
在学习并发编程时,"查看你工作的代码库"可能不是一个很好的衡量标准.并发编程是非常困难的,很少有人完全理解它,我愿意打赌绝大多数自制代码(即不使用专用的并发库)在某种程度上是不正确的.问题是这些错误可能极难观察或重现,所以你可能永远不会知道.
编辑:你是不是在你的问题说怎么了布尔的得到更新,所以我做最坏的假设.例如,如果将整个更新操作包装在全局锁中,那么当然没有并发内存访问.
多线程时遇到三个主要问题:
1)同步和线程安全.必须保护多个线程之间共享的变量不被多个线程一次写入,并防止在非原子写入期间被读取.对象的同步只能通过特殊的信号量/互斥对象来完成,该对象本身保证是原子的.volatile关键字没有帮助.
2)指令管道.CPU可以更改执行某些指令的顺序,以使代码运行得更快.在每个CPU执行一个线程的多CPU环境中,CPU管道指令而不知道系统中的另一个CPU正在执行相同操作.防止指令管道被称为记忆障碍.这一切都在维基百科上得到了解释.内存屏障可以通过专用的内存屏障对象或通过系统中的信号量/互斥对象来实现.当使用volatile关键字时,编译器可能会选择在代码中调用内存屏障,但这将是相当特殊的异常,而不是常态.我永远不会假设volatile关键字没有在编译器手册中验证它.
3)编译器不知道回调函数.就硬件中断而言,一些编译器可能不知道在代码执行过程中已经执行了一个回调函数并更新了一个值.您可以使用以下代码:
// main
x=true;
while(something) 
{   
  if(x==true)   
  {
    do_something();
  }
  else
  {
    do_seomthing_else();
    /* The code may never go here: the compiler doesn't realize that x 
       was changed by the callback. Or worse, the compiler's optimizer 
       could decide to entirely remove this section from the program, as
       it thinks that x could never be false when the program comes here. */
  } 
}
// thread callback function:
void thread (void)
{
  x=false;
}
请注意,此问题仅出现在某些编译器上,具体取决于其优化程序设置.volatile特征码解决了这个特殊问题.
因此问题的答案是:在多线程程序中,volatile关键字对线程同步/安全没有帮助,它可能不会起到内存屏障的作用,但它可以防止编译器的优化器出现危险的假设.
volatile仅在单核上使用就足够了,其中所有线程都使用相同的缓存.在多核上,如果stop()在一个核上调用并run()在另一个核上执行,则CPU缓存可能需要一些时间来进行同步,这意味着两个核可能会看到两个不同的视图isRunning_.这意味着run()它会在停止后运行一段时间.
如果您使用同步机制,它们将确保所有缓存获得相同的值,代价是暂停程序一段时间.性能或正确性对您来说更重要取决于您的实际需求.