volatile变量而不是互斥锁保护变量来检查已更改的数据

时间:2014-08-29 10:03:24

标签: c pthreads

我有一个基本上做的话:

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%,这是值得追求的。

然而,似乎非常不鼓励挥发性变量。 这种方法有什么缺点吗?任何可能导致2.版本无法正常工作的极端情况?

4 个答案:

答案 0 :(得分:2)

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

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

我无法建议观看atomic Weapons: The C++ Memory Model and Modern Hardware。 (它也适用于C语言。)

答案 1 :(得分:1)

作者:然而,似乎非常不鼓励变量变量。这种方法有什么缺点吗?任何可能导致2.版本无法正常工作的极端情况?

首先:如果您不需要atomics,那么每当您想要使用volatile时再想一想。现在看看会发生什么:

1)如果你有多个线程的循环是不安全的。您可以认为有关复制支票的信息:

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

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

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

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)小建议:除非你真的知道,你在做什么,否则不要与上帝同步。您可以从互斥锁切换到螺旋锁(由其他人编写)并进行一些基准测试。

关于修改和评论:原始答案刚开始时1)之前没有任何内容。事实证明,有些人既没有阅读完整的问题也没有完整的答案。皮蒂那些快速下来的人。这个网站不是Facebook,而且这些投票应该是有用没有帮助,这些上/下投票不像喜欢和不喜欢的Facebook!我可能不同意其他答案的某些部分,但仍然认为 有帮助,虽然不完整(不是完整的问题,只是其中的一部分)或部分incorrent(如果我们知道我们可以在没有问题的情况下做到这一点,那么从多个线程向变量写入相同的值没有什么不好的。)

答案 2 :(得分:1)

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

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

答案 3 :(得分:-1)

最好让它变得易变,以确保编译器不会进行不需要的优化,但它不会使其成为原子。

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

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

希望这有帮助。

卡尔斯。