2011-01-13 42 views
6

如果我有一些代码,看起来类似:以下指导

typedef struct { 
    bool some_flag; 

    pthread_cond_t c; 
    pthread_mutex_t m; 
} foo_t; 

// I assume the mutex has already been locked, and will be unlocked 
// some time after this function returns. For clarity. Definitely not 
// out of laziness ;) 
void check_flag(foo_t* f) { 
    while(f->flag) 
     pthread_cond_wait(&f->c, &f->m); 
} 

有什么C标准防止优化器重写check_flag为:

void check_flag(foo_t* f) { 
    bool cache = f->flag; 
    while(cache) 
     pthread_cond_wait(&f->c, &f->m); 
} 

在其他字,那么生成的代码是否有在每次遍历循环时都遵循f指针,或者编译器是否可以自由地将取消引用取出?

如果它自由拉出来,有没有什么办法来防止这种情况?我需要在某处撒上一个易挥发的关键​​字吗?它不能是check_flag的参数,因为我计划在这个结构中有其他变量,我不介意像这样编译器优化。

我可能诉诸:

void check_flag(foo_t* f) { 
    volatile bool* cache = &f->some_flag; 
    while(*cache) 
     pthread_cond_wait(&f->c, &f->m); 
} 
+0

+1在尝试通过试错法编写线程代码之前思考此类问题! – 2011-01-14 01:17:29

回答

3

通常情况下,你应该尝试的条件对象上等待的pthread_cond_wait呼叫释放互斥锁之前锁定的并行线程互斥(并在返回之前重新获取)。所以,你的check_flag函数应该被重写,以符合pthread条件的语义。

void check_flag(foo_t* f) { 
    pthread_mutex_lock(&f->m); 
    while(f->flag) 
     pthread_cond_wait(&f->c, &f->m); 
    pthread_mutex_unlock(&f->m); 
} 

关于编译与否的问题,允许优化flag领域的阅读,这answer比我解释了它的更多细节。

基本上,编译器知道的pthread_cond_waitpthread_mutex_lockpthread_mutex_unlock语义。他知道在这种情况下他不能优化记忆读数(在这个例子中呼叫pthread_cond_wait)。这里没有记忆障碍的概念,只是对特定功能的特殊了解,以及在他们面前遵循的一些规则。

还有一件事情可以保护您免受处理器执行的优化。只要语义保存,您的平均处理器就可以对内存访问进行重新排序(读/写),并且它总是这样做(因为它可以提高性能)。但是,如果多个处理器可以访问相同的内存地址,则会中断。内存屏障只是处理器的一条指令,告诉它它可以移动在屏障之前发布的读/写并在屏障之后执行它们。它已经完成了他们。

+0

这是否意味着编译器无法在寄存器中缓存“p-> some_flag”的值?我不确定内存屏障的影响。介意解释一下他们? – 2011-01-14 00:14:07

+0

编辑答案。现在更清楚了吗? – 2011-01-14 00:30:51

+0

是的,谢谢。 – 2011-01-14 00:41:11

3

正如所写的,编译器可以自由地缓存结果,或者以更加微妙的方式 - 将其放入寄存器中。您可以通过使变量volatile防止发生这种优化。但这不一定就够了 - 你不应该这样编码!您应该按照规定使用条件变量(锁定,等待,解锁)。

试图在图书馆工作很糟糕,但情况变得更糟。也许从PLDI 2005(“线程不能作为一个库实现”)或他的许多follow-on articles(引导修改后的C++内存模型)中读取Hans Boehm关于一般主题的论文会让你对上帝产生恐惧,引导你回到直线和狭窄:)。

7

在一般情况下,即使没有参与多线程和你的循环看起来像:

void check_flag(foo_t* f) { 
    while(f->flag) 
     foo(&f->c, &f->m); 
} 

编译器将无法缓存f->flag测试。这是因为编译器无法知道函数(如上面的foo())是否可能更改指向的任何对象f

在特殊情况下(foo()是可见的编译器,并传递给check_flag()所有指针被称为不被混淆或foo()以其他方式修改)编译器可能能够优化检查。

但是,pthread_cond_wait()必须以阻止优化的方式实现。

Does guarding a variable with a pthread mutex guarantee it's also not cached?

您可能也有兴趣史蒂夫·杰索普的回答:Can a C/C++ compiler legally cache a variable in a register across a pthread library call?

但是你想多远,走由贝姆的文件中提出的问题,在自己的工作是你的。据我所知,如果你想采取pthreads不能/不能做出保证的立场,那么你基本上认为pthreads是无用的(或者至少没有提供安全保证,我认为减少有相同的结果)。尽管这在最严格的意义上可能是正确的(如论文所述),但这也可能不是一个有用的答案。除了基于Unix的平台上的pthread,我不确定你会有什么选择。

1

挥发性就是为了这个目的。依靠编译器来了解pthread编码的做法似乎对我来说有点疯狂,尽管;编译器现在非常聪明。事实上,编译器可能会看到你正在循环测试一个变量,并且不会因为这个原因而将它缓存在寄存器中,而不是因为它看到你使用pthread。如果你真的在乎,就使用volatile。

有趣的小笔记。我们有一个VOLATILE #define,它或者是“volatile”(当我们认为这个bug不可能是我们的代码时)或者是空白的。当我们认为由于优化器杀死了我们而导致崩溃时,我们将其定义为“易失性”,这使得几乎所有事物都处于易失性状态。然后我们测试以确定问题是否消失。到目前为止......错误是开发者而不是编译器!谁会想到!?我们开发了一个高性能的“非锁定”和“非阻塞”线程库。我们有一个测试平台,每秒钟可以处理数千场比赛。所以票价,我们从来没有发现需要变化的问题!到目前为止,gcc从未在寄存器中缓存共享变量。呃......我们也很惊讶。我们仍然在等待我们使用易变的机会!