有一个正常循环
的线程void* Thread (void* nothing) {
while(1) {
// Sleep if requested
if ( Sleep_c)
Sys_Sleep (Sleep_c);
Do_stuff();
if (shutdown) {
shutdown_ok = 1;
break;
}
}
return 0;
}
主线程上可能会杀死它的函数
void Shutdown (void) {
shutdown = 1;
while (1) // Wait for it
if (shutdown_ok) {
shutdown = 0;
break;
}
}
现在,这在调试器上工作正常。但它在优化代码中的[关闭函数]的while(1)循环中陷入困境。为什么呢?
注意:我应该互斥锁定共享变量。
答案 0 :(得分:4)
因为编译器没有意识到shutdown_ok
将在不同线程的函数外部进行修改。也许编译器发现shutdown_ok
总是在Shutdown()
函数内评估为false,从而删除了if
语句。
void Shutdown (void)
{
shutdown = 1;
while (1)
{
shutdown = 0;
}
}
可以将变量标记为volatile
,这可以作为编译器的提示,可以通过编译器无法预测的方式修改变量。
C ++标准7.1.5.1/8: [注意:
volatile
是一个提示,以避免积极优化涉及对象,因为值对象可能会被实现无法检测的方式改变...通常,volatile
的语义在C ++中与在C中的语义相同。]
但是,编译器可能会使volatile
个变量处理标准中未指定的某些行为。例如,Visual C++ compilers make volatile
variables behave like memory locks,但实际上并没有任何标准保证这种行为。
出于这个原因,volatile
不能被视为解决所有多线程问题的灵丹妙药。对于这项工作,你最好使用适当的线程和并发原语。
答案 1 :(得分:3)
事情的最不可能的原因是,编译器不希望关闭更改,因此很乐意优化它。解决方案是使用正确的线程同步原语(如信号量或condvars)来获取您期望的行为。
人们会建议关闭volatile
,并且可以安全地假设写入int是原子的。这可能在大多数情况下有所帮助。但即便如此,你仍然可能会遇到问题。在多核计算机上,读取和写入可能会意外重新排序,从而使您丢失重要信号。英特尔有一个专门的LOCK指令来处理这些情况,但编译器通常不会生成LOCK。在ARM上你有一个DMB指令,但这也不太可能由编译器生成。
执行线程同步时的底线,使用OS原语,不要尝试推出自己的。你会得到错误的
答案 2 :(得分:1)
pthreads的解决方案是使用条件变量:
pthread_cond_t shutdown_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t shutdown_lock = PTHREAD_MUTEX_INITIALIZER;
void* Thread (void* nothing) {
while(1) {
// Sleep if requested
if ( Sleep_c)
Sys_Sleep (Sleep_c);
Do_stuff();
pthread_mutex_lock(&shutdown_lock);
if (shutdown) {
shutdown_ok = 1;
pthread_cond_signal(&shutdown_cond);
pthread_mutex_unlock(&shutdown_lock);
break;
}
pthread_mutex_unlock(&shutdown_lock);
}
return 0;
}
void Shutdown (void)
{
pthread_mutex_lock(&shutdown_lock);
shutdown = 1;
while (!shutdown_ok)
pthread_cond_wait(&shutdown_cond, &shutdown_lock);
shutdown = 0;
pthread_mutex_unlock(&shutdown_lock);
}
一般情况下,如果您发现自己想要忙碌循环,那么这表明您应该使用条件变量。
答案 3 :(得分:0)
关机的定义是什么?它在哪里定义?我怀疑这需要是volatile变量。
答案 4 :(得分:0)
我认为'shutdown_ok'的'volatile'修饰符应该避免这种情况。它告诉编译器该变量可以被另一个线程修改,以便它应该始终引用它而不是使用例如一个寄存器来保存其值的副本。