【问题标题】:Questions regarding (non-)volatile and optimizing compilers关于(非)易失性和优化编译器的问题
【发布时间】:2020-05-03 11:17:26
【问题描述】:

我有以下 C 代码:

/* the memory entry points to can be changed from another thread but 
 * it is not declared volatile */
struct myentry *entry;

bool isready(void)
{
    return entry->status == 1; 
}

bool isready2(int idx)
{
    struct myentry *x = entry + idx;        
    return x->status == 1; 
}

int main(void) {
    /* busy loop */
    while (!isready()) 
        ; 
    while (!isready2(5)) 
        ; 
}

正如我在评论中所指出的,条目声明为 volatile,即使它指向的数组可以从另一个线程(或者实际上甚至直接从内核空间)进行更改。

上面的代码不正确/不安全吗?我的想法是无法在 isready、isready2 的主体中执行任何优化,并且由于我在 main 中反复执行函数调用,因此应在每次调用时读取适当的内存位置。

另一方面,编译器可以内联这些函数。是否有可能以导致单次读取(因此导致无限循环)而不是多次读取(即使这些读取来自加载/存储缓冲区)的方式执行此操作?

还有第二个问题。是否可以通过仅在某些地方强制转换为 volatile 来防止编译器进行优化?

void func(void)
{
    entry->status = 1;
    while (((volatile struct myentry *) entry)->status != 2)
        ;
}

谢谢。

【问题讨论】:

  • 上面的代码不正确/不安全吗?是的,不安全。不能保证对entry 的更新是原子的。如果您在更新过程中阅读entry,则该值可能是无稽之谈。在您假设“无法执行优化”时可能还有很多其他问题。
  • @chux-ReinstateMonica 对。它指向的内存可以改变,而不是指针的值。
  • @AndrewHenle 对状态字段的更新之前有一个内存屏障,因此它是要更新的最后一个字段。并且在状态更改后不再更新条目(直到状态再次更改)。这会改变事情吗?
  • it is not declared volatile --> 为什么不改变struct myentry *entry; --> volatile struct myentry *entry;
  • @chux-ReinstateMonica 我只是想知道它是否真的“正确”。例如。 Linus 在这里 (lwn.net/Articles/233482) 说以下似乎是有道理的: """ 在 C 中,“数据”是易变的,但那是疯狂的。数据不是易变的 - 访问 是易变的。所以说“让这个特定的访问小心”可能是有意义的,但不是“让对这个数据的所有访问都使用一些随机策略”。“”“

标签: c multithreading volatile


【解决方案1】:

如果entry 指向的内存可以被另一个线程修改,则程序存在数据竞争,因此行为未定义。即使使用volatile 也是如此。

要让多个线程同时访问一个变量,在 ISO C11 中,它必须是 atomic type,或者受到正确同步的保护。

如果使用较旧的标准版本,则标准不提供关于多线程的保证,因此您将受到编译器任何特殊行为的摆布。

如果使用 POSIX 线程,则没有可移植的原子操作,但它确实定义了同步原语。

另见:

第二个问题是个问题,我建议不要这样做,因为不同的编译器可能会以不同的方式解释含义,并且无论哪种方式,行为仍然是形式上未定义的。

【讨论】:

  • 让我更详细地解释一下我的情况。我只有 2 个线程 A、B。A 只发布条目,B 只异步使用它们。 B 不断扫描数组(本质上是进行轮询)检查条目是否具有状态 SUBMITTED 并且在这种情况下使用它并将其状态更改为 FREE。线程 A 还扫描数组以查找 FREE 条目,更新其字段,执行内存屏障,然后将状态更改为 SUBMITTED(从而确保最后写入状态更改)。我认为在这种情况下不可能发生数据竞争,我只担心在状态字段上进行轮询。
  • @ilstam 我无法在没有看到同步代码的情况下评论它的正确性,也许您可​​以发布一个包含您实际代码的新问题
  • 我忘了说线程 B 在更改状态之前也使用了内存屏障。因此不存在竞争条件,因为只有 2 个线程在更改数据之前等待不同的状态值。
  • 好的,也许我可以尝试编写一个最小的工作示例。
  • @ilstam 是个好主意,我建议提出一个新问题而不是编辑这个问题,因为已经询问并回答了阅读代码没有同步的这个问题
猜你喜欢
  • 2012-12-19
  • 2019-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-26
  • 1970-01-01
  • 1970-01-01
  • 2010-10-20
相关资源
最近更新 更多