【问题标题】:volatile variable instead of mutex protected variable to check for changed datavolatile 变量而不是互斥保护变量来检查更改的数据
【发布时间】:2014-08-29 10:03:24
【问题描述】:

我有一个基本上可以做到的线程:

int changed; //global variable
..

for (;;) {
   pthread_mutex_lock(&mtx);
   if (changed) {
       do_changes();
       changed = 0;
   }
   pthread_mutex_unlock(&mtx);

   do_stuff();

}

循环每秒运行几十万次,而全局变量changed 很少被另一个线程设置(一天几次)。

改成

volatile int changed; //global variable
..

for (;;) {

   if (changed) {
       pthread_mutex_lock(&mtx);
       do_changes();
       changed = 0;
       pthread_mutex_unlock(&mtx);
   }
   do_stuff();
}

我可以用这种方法测量循环性能提高 3-4%,值得追求。

然而 volatile 变量似乎被严重劝阻。 这里的方法有什么缺点吗?任何可能导致 2. 版本无法按预期工作的极端情况?

【问题讨论】:

  • 你确定性能提升是因为使用volatile而不是因为do_stuff()(或代码的其他部分)在不同的运行中做不同的事情吗?
  • 如果循环只由一个线程运行是好的,如果不是,重新检查锁定后的值。考虑改用 std::atomic_flag。
  • @KingsIndian 是的,我很确定。
  • 是否需要在保持互斥锁锁定的同时调用do_changes
  • @MaximYegorushkin 是的。

标签: c pthreads


【解决方案1】:

volatile 不会使您的变量线程安全或原子。您可能想使用C11 atomics

您基本上有两个线程更改 changed 变量,从而覆盖以前的值导致数据竞争。

我不能推荐足够的观看atomic Weapons: The C++ Memory Model and Modern Hardware。 (它也适用于 C)。

【讨论】:

  • 没有锁定没有人更改changed 变量。请注意,更改后的变量只能在没有锁的情况下读取,而不是写入。它只有 2 个重要的值。 0 或 1。即,它仅用于指示某些情况发生了变化。检查是否在与其他线程更改相同的迭代或下一次迭代中发现更改是无关紧要的。
  • 实际上不需要 volatile,它没用有两个原因:1. pthread_mutex 强制执行内存围栏,2. 我们不在循环中(检查缓存值)。问题是我们如何使用变量?如果它仅由一个线程检查并且更新时间并不重要(只需要在某个时间点完成),那么一切都很好(从多个线程写入它不是问题 - 在锁定下完成内存栅栏 - 读取是可能的问题 - 可能不会立即看到更改)
  • 我必须纠正自己:如果 do_stuff() 是微不足道的(内联),则需要 volatile。然后我们处于一个循环中并且从不调用 pthread_mutex_lock (强制执行mfence 或类似的)。
【解决方案2】:

如果您使用支持它们的 C11 环境,您可以使用atomic variables。如果您的系统支持它,它们会使用特殊指令来实现原子性,而不是锁定。如果您的系统不支持,它们会使用锁(标志类型始终是无锁的)。

如果您没有 C11,但您有 GCC 兼容的编译器,请参阅 sync 系列函数。它与 C11 原子变量类似(但较旧),但如果您的系统不支持它们,它们会生成函数调用。

【讨论】:

    【解决方案3】:

    作者:然而 volatile 变量似乎被严重劝阻。这里的方法有什么缺点吗?任何可能导致 2. 版本无法按预期工作的极端情况?

    首先:当您想使用volatile 时,请再考虑一下是否不需要atomics 现在看看会发生什么:

    1) 如果在多个线程中有循环,则不安全。您可以考虑复制支票:

    if (changed) { // quick check
       pthread_mutex_lock(&mtx);
       if (changed) { // another thread could do the work
           ...
    

    2) 如果您的代码对于看到它已更改至关重要,则需要使用原子,因为 pthread_mutex_lock 之前的 if(changed) 可能因为缓存而看不到它。

    3) 它可以在 x86(_64) 上使用强内存排序和原子 int 访问,但在其他架构上失败。这就是为什么不鼓励volatile 使用原子(并养成它的习惯)的原因。 volatile 不强制使用原子或任何其他同步。原子做(读-修改-写指令)。

    std::atomic_flag validated;
    std::mutex mx; struct MyData { ... } data;
    void change() {
        lock_guard<mutex> lock(mx);
        data.something();
        validated.clear();
    }
    void validate() {
        if(!validated.test_and_set()) {
            lock_guard<mutex> lock(mx);
            data.update();
        }
    }
    

    注意:除非您持有锁并为其使用另一个变量,否则您永远无法确定数据是否有效。

    4) 只需使用 pthread_spinlock_t 尝试您的原始代码

    5) 小建议:除非你真的知道自己在做什么,否则不要用同步来扮演上帝。您可以从互斥锁切换到自旋锁(由其他人编写)并进行一些基准测试。

    关于编辑和 cmets: 最初的答案只是从 1) 开始。事实证明,有些人既没有阅读完整的问题,也没有阅读完整的答案。可怜那些快速投票的人。这个网站不是 facebook,这些投票应该是 有帮助没有帮助,这些赞成/反对票不是 facebook 上的喜欢和不喜欢!我可能不同意其他答案的某些部分,但仍然认为它们有帮助,虽然不完整(没有回答完整的问题,但只是部分回答)或部分不正确(如果我们知道我们可以毫无问题地做到这一点,那么从多个线程向变量写入相同的值并没有什么不好)。

    【讨论】:

    • 正如所写的那样,这个答案似乎给 solutions 不是“显示问题”,因为它不是以“您可能会考虑这样做......但是不起作用,因为……”。此外,(1)被破坏的原因不仅仅是缓存效果,请参阅aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf 中的讨论,指出volatile 可能对线程间优化没有影响
    • @JonathanWakely:作者的问题以 这里的方法有什么缺点吗?任何可能导致 2. 版本无法按预期工作的极端情况? 我的回答也是如此,一个可能的问题。我们不知道它是如何使用的,它实际上可以按预期工作,但需要满足其他条件(单线程进行更新,不需要立即更新,只需一段时间)。
    【解决方案4】:

    最好将其设置为 volatile 以确保编译器不会进行不需要的优化,但它不会使其成为原子。

    如果有更多线程读取“已更改”,则当有另一个线程等待执行 do_changes() 时,可能会发生一个线程将“已更改”更新为 0,这将在互斥锁被释放后发生,因为条件已被评估。

    如果您想避免这种情况,请将 if 语句移动到互斥保护空间内。

    希望这会有所帮助。

    卡尔斯。

    【讨论】:

    • 在多线程中使用volatile确实是个坏主意,不要那样做。
    • 尽管投了反对票,但volatile 对我们来说是安全的>”你自己,所以,那些反对者可能无法正确阅读。 +1 作为平衡。
    • @firda volatile 对于多线程来说既不安全也不足够,它需要大师级工程师了解架构并正确使用它。即使这样,使用volatile 也会阻止编译器进行优化,而这些优化是在使用原子时完成的。你可能喜欢看那个“原子武器”的视频,真的是大开眼界。
    • @MaximYegorushkin:我知道 volatile 的缺点(因为它在旧 PC 上工作,所以被视为原子)。我同意最好不要扮演上帝并在他们所属的地方使用原子。但我不同意投反对票,仅此而已 - 在投反对票太快之前先阅读完整的句子;)
    • @MaximYegorushkin 如果您可以谈论与相关代码相关的细节而不是笼统地谈论,那就太好了。请注意,有问题的变量不会在没有锁的情况下写入,不依赖于原子读取或写入的变量 - 有人提出的唯一真正关心的问题似乎是变量是否在多个线程中检查(这不是案例)。
    猜你喜欢
    • 2012-05-25
    • 2019-01-08
    • 1970-01-01
    • 1970-01-01
    • 2011-03-13
    • 1970-01-01
    • 2017-12-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多