我有一个环形缓冲区,由一个生产者写入并由 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。在我看来,这个值可能在cmpandcmovl指令之间发生了变化。如果我是正确的,那么以下情况是可能的:
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