没有std :: atomics的无锁哈希是否保证在C++ 11中是线程安全的?

Tem*_*Rex 2 c++ multithreading atomic lockless c++11

考虑以下针对多线程搜索算法的无锁散列表的尝试(受本文启发)

struct Data
{
    uint64_t key;
    uint64_t value;
};

struct HashEntry
{
    uint64_t key_xor_value;
    uint64_t value;
};

void insert_data(Data const& e, HashEntry* h, std::size_t tableOffset)
{
    h[tableOffset].key_xor_value = e.key ^ e.value;
    h[tableOffset].value = e.value;
}

bool data_is_present(Data const& e, HashEntry const* h, std::size_t tableOffset)
{
    auto const tmp_key_xor_value = h[tableOFfset].key_xor_value;
    auto const tmp_value = h[tableOffset].value;

    return e.key == (tmp_key_xor_value ^ tmp_value);
}
Run Code Online (Sandbox Code Playgroud)

这个想法是一个HashEntrystruct存储一个struct的两个64位字的XOR-ed组合Data.如果两个线程对结构的两个64位字进行交错读/写HashEntry,那么想法是读取线程可以通过再次进行异或并与原始进行比较来检测key.因此,可能由于损坏的哈希条目而导致效率损失,但是在解码的检索到的密钥与原始密钥匹配的情况下仍然保证了正确性.

该文件提到它基于以下假设:

对于本讨论的其余部分,假设64位存储器读/写操作是原子的,即在一个周期内读/写整个64位值.

我的问题是:上面的代码没有明确使用std::atomic<uint64_t>保证在C++ 11中是线程安全的吗?或者可以通过同时读/写来破坏各个64位字?即使在64位平台上?这与旧的C++ 98标准有何不同?

标准的行情将非常感谢.

更新:基于Hans Boehm关于"良性"数据竞赛的这篇惊人论文,一个简单的方法就是让编译器取消两个XOR insert_data() 并且data_is_present()总是返回true,例如,如果它找到像本地代码片段那样

insert_data(e, h, t);
if (data_is_present(e, h, t)) // optimized to true as if in single-threaded code
   read_and_process(e, h, t); // data race if other thread has written
Run Code Online (Sandbox Code Playgroud)

Nic*_*las 7

C++ 11规范几乎定义了一个线程读取或写入另一个线程正在写入的内存位置的任何尝试作为未定义的行为(没有使用原子或互斥锁来防止来自一个线程的读/写而另一个线程是写作).

个别编译器可能会使其安全,但C++ 11规范本身并不提供覆盖.同时读取从来都不是问题; 它在一个线程中写入,而在另一个线程中读取/写入.

这与旧的C++ 98标准有何不同?

C++ 98/03标准没有提供有关线程的任何内容.就C++ 98/03内存模型而言,线程不是可能发生的事情.

  • @rhalbersma:他们可能很幸运.他们可能已经检查了汇编代码.但这非常非常糟糕.下一版本的编译器甚至CPU升级可能会破坏这种代码.如果您绝对需要性能,如基准测试等所示,它可能是您"最不可怕"的选择.但没有人应该开始这样做. (2认同)
  • @rhalbersma - 有两个**问题原子类型的地址:**撕裂**(当只写入或读取部分值时切换线程)和**可见性**(确保其他线程看到变化在写入值的线程中创建).即使保证64位存储仅需要一个总线周期,也不能解决可见性问题.正如其他人所说,C++标准非常明确:在另一个线程正在读取时写入某个位置,缺少**语言级别同步**,会产生未定义的行为. (2认同)