我正在基于论文"弱记忆模型的正确和高效的工作窃取"来实现Chase-lev deque。在论文中,它要求双端具有原子元素的缓冲区:
struct Deque {
std::atomic<size_t> size;
std::atomic<int> buffer[];
}
为什么缓冲区中的元素类型为std::atomic<int>
而不是普通int
?
好吧,因为缓冲区元素由不同的线程读取/写入,并且写入和后续读取之间并不总是具有先行关系。因此,如果缓冲区元素不是原子的,您将面临数据竞争。
如果你有兴趣,可以看看我的 chase-lev-deque 实现: https://github.com/mpoeter/xenium/blob/master/xenium/chase_work_stealing_deque.hpp
更新
问题是索引可以环绕。假设调用 steal 的线程可能在读取top
和bottom
之后,但在它可以从缓冲区读取项目之前被挂起。如果在此期间索引环绕,则该项目可能会被某些推送操作覆盖。因此,窃取中的加载操作不会与该存储发生之前的关系。
该标准对数据竞争的定义如下:
如果程序在不同线程中包含两个冲突的操作,则程序的执行包含数据争用,其中至少一个不是原子的,并且两者都不会先于另一个发生。
由于所描述的示例没有提供缓冲区上的读取和写入操作之间的发生前关系,因此如果缓冲区不是原子的,这将是数据争用。但是,原子永远不能参与数据竞争,因此通过使缓冲区原子化,我们完全防止了由不同步访问引起的任何数据竞争(即使此类操作被放宽(。
请注意,这仅在防止缓冲区操作的数据争用时才需要。推送和窃取操作之间的实际同步是通过底部的操作进行的。