我有一个环形缓冲区,由一个生产者写入并由 N 个消费者读取。因为它是一个环形缓冲区,所以生产者写入的索引小于消费者当前的最小索引是可以的。生产者和消费者的位置由他们自己跟踪Cursor
。
class Cursor
{
public:
inline int64_t Get() const { return iValue; }
inline void Set(int64_4 aNewValue)
{
::InterlockedExchange64(&iValue, aNewValue);
}
private:
int64_t iValue;
};
//
// Returns the ringbuffer position of the furthest-behind Consumer
//
int64_t GetMinimum(const std::vector<Cursor*>& aCursors, int64_t aMinimum = INT64_MAX)
{
for (auto c : aCursors)
{
int64_t next = c->Get();
if (next < aMinimum)
{
aMinimum = next;
}
}
return aMinimum;
}
查看生成的汇编代码,我看到:
mov rax, 922337203685477580 // rax = INT64_MAX
cmp rdx, rcx // Is the vector empty?
je SHORT $LN36@GetMinimum
npad 10
$LL21@GetMinimum:
mov r8, QWORD PTR [rdx] // r8 = c
cmp QWORD PTR [r8+56], rax // compare result of c->Get() and aMinimum
cmovl rax, QWORD PTR [r8+56] // if it's less then aMinimum = result of c->Get()
add rdx, 8 // next vector element
cmp rdx, rcx // end of the vector?
jne SHORT $LL21@GetMinimum
$LN36@GetMinimum:
fatret 0 // beautiful friend, the end
我看不出编译器如何认为可以读取 的值c->Get()
,将其与 进行比较aMinimum
,然后有条件地将 RE-READ 的值移动c->Get()
到aMinimum
。在我看来,这个值可能在cmp
andcmovl
指令之间发生了变化。如果我是正确的,那么以下情况是可能的:
aMinimum
当前设置为 2c->Get()
返回 1完成
cmp
并设置less-than
标志另一个线程将当前持有的值更新
c
为 3cmovl
设置aMinimum
为 3Producer 看到 3 并覆盖 ringbuffer 的位置 2 中的数据,即使它还没有被处理。
我是不是看得太久了?不应该是这样的:
mov rbx, QWORD PTR [r8+56]
cmp rbx, rax
cmovl rax, rbx