Cla*_*bel 6 c multithreading pthreads c99 compiler-optimization
如果我有一些看起来像这样的代码:
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);
}
Run Code Online (Sandbox Code Playgroud)
C标准中是否有任何内容阻止优化器将check_flag重写为:
void check_flag(foo_t* f) {
bool cache = f->flag;
while(cache)
pthread_cond_wait(&f->c, &f->m);
}
Run Code Online (Sandbox Code Playgroud)
换句话说,每次循环时生成的代码是否必须跟随f指针,或者编译器是否可以自由地取消引用?
如果它可以自由拉出来,有什么办法可以防止这种情况发生吗?我需要在某处撒一个volatile关键字吗?它不能是check_flag参数,因为我计划在这个结构中有其他变量,我不介意编译器这样优化.
可能我不得不诉诸:
void check_flag(foo_t* f) {
volatile bool* cache = &f->some_flag;
while(*cache)
pthread_cond_wait(&f->c, &f->m);
}
Run Code Online (Sandbox Code Playgroud)
在一般情况下,即使没有涉及多线程,你的循环看起来像:
void check_flag(foo_t* f) {
while(f->flag)
foo(&f->c, &f->m);
}
Run Code Online (Sandbox Code Playgroud)
编译器将无法缓存f->flag测试.那是因为编译器无法知道函数(foo()如上所述)是否可能会改变任何对象f所指向的内容.
在特殊情况下(foo()编译器可以看到,并且所有传递给它的指针check_flag()都知道没有别名或者可以修改foo()),编译器可能能够优化检查.
但是,pthread_cond_wait()必须以防止优化的方式实施.
请参阅使用pthread互斥锁保护变量是否保证它也没有缓存?:
您可能也对Steve Jessop的回答感兴趣:C/C++编译器是否可以通过pthread库调用合法地在寄存器中缓存变量?
但是,你想在你自己的工作中把Boehm论文提出的问题放在多远,取决于你.据我所知,如果你想采取pthreads没有/不能做出保证的立场,那么你本质上就认为pthreads是无用的(或至少不提供安全保证,我认为通过减少有相同的结果).虽然这可能是最严格意义上的(如论文中所述),但它也可能不是一个有用的答案.我不确定除了基于Unix的平台上的pthreads之外你还有什么选择.
通常,您应该在等待条件对象之前尝试锁定 pthread 互斥体,因为调用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);
}
Run Code Online (Sandbox Code Playgroud)
关于是否允许编译器优化字段读取的问题flag,这个答案比我解释得更详细。
pthread_cond_wait基本上,编译器知道,pthread_mutex_lock和的语义pthread_mutex_unlock。他知道在这种情况下他无法优化内存读取(本例中的调用pthread_cond_wait)。这里没有记忆障碍的概念,只是对某些功能的特殊知识,以及它们存在时要遵循的一些规则。
还有一件事可以保护您免受处理器执行优化的影响。如果语义保持不变,您的普通处理器能够重新排序内存访问(读/写),并且它总是这样做(因为它可以提高性能)。但是,当多个处理器可以访问同一内存地址时,就会出现这种情况。内存屏障只是向处理器发出的一条指令,告诉处理器可以移动屏障之前发出的读/写操作并在屏障之后执行它们。现在他们已经完蛋了。
| 归档时间: |
|
| 查看次数: |
238 次 |
| 最近记录: |