在以下代码中:
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int ready = 0;
wait()
{
int i;
do
{
usleep(1000);
pthead_mutex_lock(&mutex);
i = ready;
pthread_mutex_unlock(&mutex);
} while (i == 0);
printf("Finished\n");
}
signal()
{
pthead_mutex_lock(&mutex);
ready = 1;
pthread_mutex_unlock(&mutex);
}
我们生成两个线程,我们在一个线程中调用wait,然后在另一个线程中调用signal我们也让编译器积极地进行优化。
现在代码的行为是否符合预期,或者我们是否需要使用volatile来使其工作?不同的编译器和库会以不同的方式处理它吗?
编辑:我希望互斥函数可能存在一些阻止优化的问题,或者编译器通常不会优化循环函数调用。
注意:我还没有编译和测试代码,如果有机会的话会这样做。
答案 0 :(得分:2)
如果编译器在存在库函数调用时假设有关全局变量的任何内容,我会感到惊讶。话虽这么说,不稳定不会花费你任何东西,它显示了你的意图。我把它放在那里。
答案 1 :(得分:2)
答案 2 :(得分:1)
挥发既不是必需的,也不是充分的。所以没有理由使用它。这是不够的,因为没有标准指定它将提供线程之间的可见性。没有必要,因为pthreads标准说互斥量就足够了。
更糟糕的是,使用它表明你是一个不称职的程序员,他试图在你的代码上撒上魔法粉尘来使它工作。它充斥着货物崇拜节目,任何看过代码的人都会推断出你不知道它不是必需的。更糟糕的是,他们可能认为你认为这已经足够了,并且他们会怀疑你编写的任何其他代码,担心你使用“volatile”来隐藏多线程错误而不是修复它们。
答案 3 :(得分:0)
现在代码的行为是否符合预期,或者我们是否需要使用volatile来使其工作?
我建议在这种情况下使用volatile
。虽然在这种情况下似乎不需要它。
IOW,我个人会添加volatile
并删除锁定:设置/读取变量的值不需要它。
不同的编译器和库会以不同的方式处理吗? 我希望互斥函数可能有一些东西会阻止自身优化,或者编译器通常不会优化循环函数调用。
在您的情况下,对函数的调用(pthread_mutex_lock())具有副作用并更改执行环境。因此编译器必须重新读取可能通过调用函数而改变的全局变量。
确实,你想咨询C99的5.1.2.3 Program execution
,我借用了这个术语。给你的味道:
[...]访问易失性对象,修改对象,修改文件或调用执行任何这些操作的函数都是副作用,它们是状态的变化执行环境。表达的评估可能产生副作用。在执行序列中称为序列点的某些特定点,先前评估的所有副作用应完整,并且不会发生后续评估的副作用。 (序列点的摘要见附件C.)
在抽象机器中,所有表达式都按语义指定进行评估。实际实现不需要评估表达式的一部分,如果它可以推断出它的值未被使用并且不产生所需的副作用(包括由调用函数或访问易失性对象引起的任何副作用)。 [...]
摘自附件C:
以下是5.1.2.3中描述的序列点:
- 在评估参数之后调用函数(6.5.2.2)。
从此开始。
根据我的经验,编译器现在足够聪明,即使在积极优化时也不会对循环控制变量(全局变量)做任何事情。
答案 4 :(得分:-1)
是的,这里总是需要volatile int ready = 0;
。
<强>更新强>
如果您不想围绕某些代码片段进行优化,可以在该代码周围使用#pragma
指令(如果您的编译器支持它们)或者 - 这是完全可移植的 - 将代码移动到单独的文件并编译这些文件或者很少的优化
后一种方法可能仍然需要使用volatile
关键字,因为ready
变量可能会被您可能想要优化的其他模块使用。
答案 5 :(得分:-1)
在优化存在的情况下需要挥发性。否则,就绪的读取可以合法地移出while循环。
假设标准未承诺的优化限制现在可能没问题,但随着编译器的改进,将给未来的维护者带来极大的悲痛。