0

说我有

bool unsafeBool = false;

int main()
{
    std::thread reader = std::thread([](){ 
        std::this_thread::sleep_for(1ns);
        if(unsafeBool) 
            std::cout << "unsafe bool is true" << std::endl; 
    });
    std::thread writer = std::thread([](){ 
        unsafeBool = true; 
    });
    reader.join();
    writer.join();
}

保证作者写完后就unsafeBool变成了。true我知道阅读器输出的是未定义的行为,但据我所知,写入应该没问题。

4

3 回答 3

1

writer.join()它保证之后unsafeBool == true。但在阅读器线程中,对它的访问是一场数据竞赛。

于 2020-09-12T07:22:46.120 回答
1

UB 是并且仍然是 UB,可以推理为什么事情会以它们发生的方式发生,但是,你不能依赖它。

您有竞争条件,可以通过以下方式解决它:添加锁或将类型更改为原子。

由于您的代码中有 UB,因此您的编译器可以假设这不会发生。如果它可以检测到这一点,它可以将您的完整函数更改为 noop,因为它永远不会在有效程序中调用。

如果不这样做,则行为将取决于您的处理器和链接到它的缓存。在那里,如果连接之后的代码使用与读取布尔值的线程相同的核心(在连接之前),您甚至可能在那里仍然有 false 而无需使缓存无效。

在实践中,使用 Intel X86 处理器,您不会看到竞争条件产生的很多副作用,因为它已使写入时缓存无效。

于 2020-09-12T07:35:17.417 回答
0

一些实现保证任何尝试读取在其更改时未限定的字长或更小的对象的volatile值将产生任意选择的旧值或新值。在这种保证有用的情况下,编译器持续支持它的成本通常低于解决它的缺失的成本(除其他外,因为程序员可以解决它的缺失的任何方法都会限制编译器的自由选择旧值或新值)。

然而,在其他一些实现中,即使看起来应该涉及对值的单次读取的操作也可能会产生将多次读取的结果组合在一起的代码。当使用命令行参数调用 ARM gcc 9.2.1-xc -O2 -mcpu=cortex-m0并给出:

#include <stdint.h>
#include <string.h>
#if 1
uint16_t test(uint16_t *p)
{
    uint16_t q = *p;
    return q - (q >> 15);
}

它生成从 读取*p然后从读取的代码*(int16_t*)p,将后者右移 15,并将其添加到前者。如果 的值*p在两次读取之间发生变化,这可能会导致函数返回 0xFFFF,这是一个不可能的值。

不幸的是,许多设计编译器以便他们始终避免以这种方式“拆分”读取的人认为这种行为足够自然和明显,没有特别的理由明确记录他们从不做任何其他事情的事实。同时,其他一些编译器编写者认为,因为标准允许编译器拆分读取,即使没有理由(在上面的代码中拆分读取使其比仅读取一次值时更大和更慢)任何代码将依赖于避免这种“优化”的编译器是“损坏的”。

于 2020-09-15T16:55:20.710 回答