遵循多线程环境中的指针

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)

Mic*_*urr 7

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

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之外你还有什么选择.


Syl*_*sne 3

通常,您应该在等待条件对象之前尝试锁定 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)。这里没有记忆障碍的概念,只是对某些功能的特殊知识,以及它们存在时要遵循的一些规则。

还有一件事可以保护您免受处理器执行优化的影响。如果语义保持不变,您的普通处理器能够重新排序内存访问(读/写),并且它总是这样做(因为它可以提高性能)。但是,当多个处理器可以访问同一内存地址时,就会出现这种情况。内存屏障只是向处理器发出的一条指令,告诉处理器可以移动屏障之前发出的读/写操作并在屏障之后执行它们。现在他们已经完蛋了。

  • 编译器无法缓存该值与内存屏障或线程无关;这只是调用可以修改标志这一事实的结果。从编译器的角度来看,可能修改了该标志的是对“pthread_cond_wait”的实际调用,而不是其他线程。 (3认同)