相关疑难解决方法(0)

读者/写者锁...没有读者锁?

我感觉这可能是一种非常普遍和常见的情况,对此有一个众所周知的无锁解决方案。

简而言之,我希望有一种像读取器/写入器锁这样的方法,但这不需要读取器获取锁,因此可以获得更好的平均性能。

相反,对于读取器来说会有一些原子操作(128 位 CAS),对于写入器来说会有一个互斥锁。我有数据结构的两个副本,一个用于正常成功查询的只读副本,以及一个要在互斥锁保护下更新的相同副本。将数据插入可写副本后,我们将其设为新的可读副本。一旦所有待处理的读取器都读完它,旧的可读副本就会被依次插入,写入器会旋转剩余的读取器数量,直到其为零,然后依次修改它,最后释放互斥体。

或类似的东西。

存在这样的东西吗?

c++ concurrency lock-free lockless stdatomic

6
推荐指数
2
解决办法
2740
查看次数

C++ 原子和带有 RDMA 的内存顺序

当在现代无锁内存上使用单侧 RDMA 时,会出现这样的问题:如果数据对象跨越多个缓存行,远程读取器如何安全地查看其传入数据。

\n

在 Derecho 开源多播和复制日志库(位于https://GitHub.com/Derecho-Project上)中,我们有这种模式。写入器 W 被授予写入读取器 R 中的一系列内存的权限。内存已正确固定和映射。现在,假设写入涉及跨越许多缓存行的某种数据向量,这很常见。我们使用一个守卫:一个递增的计数器(也在 RDMA 可访问内存中,但在其他一些缓存行中)。R 旋转,当它看到变化时观察计数器\xe2\x80\xa6,这告诉 R \xe2\x80\x9c 你有一条新消息\xe2\x80\x9d,然后 R 读取向量中的数据。后来我们有第二种模式,R 对 W 说,\xe2\x80\x9c我已经处理完该消息,你可以发送另一条消息。\xe2\x80\x9d

\n

我的问题:对于现代内存模型,应该使用哪种 C++ 原子风格来写入向量的内存?这会被称为宽松一致性吗?我希望我的代码能够在 ARM 和 AMD 上运行,而不仅仅是具有强大 TSO 内存模型的英特尔。

\n

那么对于我的计数器,当 R 旋转监视计数器更新时,我希望如何声明计数器?是否需要将其声明为获取-释放原子?

\n

最后,在 R 观察到计数器已增加之后,就速度或正确性而言,将所有内容声明为宽松的,然后在此处使用内存顺序栅栏是否有任何优点?我的想法是,通过第二种方法,我在所有 RDMA 内存上使用最小一致性模型(并对所有此类内存使用相同的模型),而且我只需要在观察到计数器增加后调用成本更高的内存顺序栅栏。因此,在访问我的向量之前,它只发生一次,而每次我的轮询线程循环时,获取释放原子计数器都会触发内存防护机制。对我来说,这听起来非常昂贵。

\n

最后一个想法又引出了一个问题:我是否也必须将此内存声明为易失性,以便 C\xe2\x80\x94 编译器意识到数据可以在其脚下更改,或者编译器本身可以看到数据就足够了std::原子类型声明?在Intel上,对于全店订购,肯定需要TSO加上易失性。

\n

[编辑:新信息](我试图在这里吸引一些帮助!)

\n

一种选择似乎是将 RDMA 内存区域声明为 std::atomic<relaxed_consistency> 但每次我们的谓词评估线程重新测试防护时都使用锁(在 RDMA 内存中,将使用相同的宽松属性进行声明) )。我们将保留 C++ 易失性注释。

\n

原因是,使用具有获取-释放语义的锁,内存一致性硬件将被警告它需要隔离先前的更新。锁本身(互斥体)可以声明为谓词线程本地的,然后将存在于本地 DRAM 中,这是便宜的,并且由于这不是任何东西争用的锁,因此锁定它可能与 test_and_set 一样便宜,并且解锁只是写入 0。如果谓词为 true,我们的触发代码体将在访问锁之后运行(可能是在锁释放之后),因此我们建立所需的顺序以确保硬件将获取受保护的对象使用实际的内存读取。但是,通过谓词测试的每个周期(每次“旋转”),我们最终都会对每个谓词执行锁定获取/释放。所以这会导致一些速度减慢。

\n

选项二看似开销较小,也将 RDMA 区域声明为具有宽松一致性的 std::atomic,但省略了锁并像我们现在一样进行测试。然后,当谓词测试为真时,我们将使用语义执行显式内存栅栏(std::memory-order)。我们得到相同的屏障,但仅在谓词评估为 true 时才支付成本,因此开销更少。

\n

但现在我们遇到了一个不同类型的问题。Intel 有总存储顺序 TSO,并且由于任何线程都会执行一些先写后读操作,Intel …

c++ cpu-architecture memory-barriers rdma stdatomic

5
推荐指数
1
解决办法
360
查看次数

在固定不同 CPU 的 2 个线程之间传递一些变量的最佳方式

我有一个问题需要了解是否有更好的解决方案。我编写了以下代码,将一些变量从编写器线程传递到读取器线程。这些线程固定到共享相同 L2 缓存的不同 CPU(禁用超线程)。

writer_thread.h

struct a_few_vars {
    uint32_t x1;
    uint32_t x2;

    uint64_t x3;
    uint64_t x4;
} __attribute__((aligned(64)));

volatile uint32_t head;
struct a_few_vars xxx[UINT16_MAX] __attribute__((aligned(64)));
Run Code Online (Sandbox Code Playgroud)

reader_thread.h

uint32_t tail;
struct a_few_vars *p_xxx;
Run Code Online (Sandbox Code Playgroud)

写入线程增加头变量,读取线程检查头变量和尾变量是否相等。如果它们不相等,则按如下方式读取新数据

while (true) {
    if (tail != head) {
        .. process xxx[head] ..
        .. update tail ..
    }
}
Run Code Online (Sandbox Code Playgroud)

性能是迄今为止最重要的问题。我使用的是 Intel Xeon 处理器,读取器线程每次都会从内存中获取 head 值和 xxx[head] 数据。我使用对齐数组来实现无锁

就我而言,是否有任何方法可以尽快将变量刷新到读取器CPU缓存中。我可以从写入器 CPU 触发读取器 CPU 的预取吗?如果存在的话,我可以使用 __asm__ 来使用特殊的英特尔指令。总之,在固定到不同 CPU 的线程之间传递结构中的变量的最快方法是什么?

提前致谢

c x86 intel memory-alignment cpu-cache

2
推荐指数
1
解决办法
246
查看次数

用 32 位原子实现 64 位原子计数器

我想从原子 uint32s 拼凑一个 uint64 原子计数器。计数器有一个写入器和多个读取器。编写器是一个信号处理程序,所以它不能阻塞。

我的想法是使用低位的代数作为读锁。读取器重试,直到整个读取过程中生成计数稳定,并且低位未设置。

以下代码在内存排序的设计和使用中是否正确?有没有更好的办法?

using namespace std;
class counter {
    atomic<uint32_t> lo_{};
    atomic<uint32_t> hi_{};
    atomic<uint32_t> gen_{};

    uint64_t read() const {
        auto acquire = memory_order_acquire;
        uint32_t lo, hi, gen1, gen2;
        do {
            gen1 = gen_.load(acquire);
            lo = lo_.load(acquire);
            hi = hi_.load(acquire);
            gen2 = gen_.load(acquire);
        } while (gen1 != gen2 || (gen1 & 1));
        return (uint64_t(hi) << 32) | lo;
    }

    void increment() {
        auto release = memory_order_release;
        gen_.fetch_add(1, release);
        uint32_t newlo = 1 + lo_.fetch_add(1, release);
        if (newlo …
Run Code Online (Sandbox Code Playgroud)

c++ lockless c++11 stdatomic seqlock

1
推荐指数
1
解决办法
1029
查看次数