SPSC lock free queue without atomics

本秂侑毒 提交于 2019-12-09 19:12:12

问题


I have below a SPSC queue for my logger.

It is certainly not a general-use SPSC lock-free queue.

However, given a bunch of assumptions around how it will be used, target architecture etc, and a number of acceptable tradeoffs, which I go into detail below, my questions is basically, is it safe / does it work?

  • It will only be used on x86_64 architecture, so writes to uint16_t will be atomic.
  • Only the producer updates the tail.
  • Only the consumer updates the head.
  • If the producer reads an old value of head, it will look like there is less space in the queue than reality, which is an acceptable limitation in the context in which is is used.
  • If the consumer reads an old value of tail, it will look like there is less data waiting in the queue than reality, again an acceptable limitation.

The limitations above are acceptable because:

  • the consumer may not get the latest tail immediately, but eventually the latest tail will arrive, and queued data will be logged.
  • the producer may not get the latest head immediately, so the queue will look more full than it really is. In our load testing we have found the amount we log vs the size of the queue, and the speed at which the logger drains the queue, this limitation has no effect - there is always space in the queue.

A final point, the use of volatile is necessary to prevent the variable which each thread only reads from being optimised out.

My questions:

  • Is this logic correct?
  • Is the queue thread safe?
  • Is volatile sufficient?
  • Is volatile necessary?

My queue:

class LogBuffer
{
public:
    bool is_empty() const { return head_ == tail_; }
    bool is_full()  const { return uint16_t(tail_ + 1) == head_; }
    LogLine& head()       { return log_buffer_[head_]; }
    LogLine& tail()       { return log_buffer_[tail_]; }
    void advance_head()   { ++head_; }
    void advance_hail()   { ++tail_; }
private:
    volatile uint16_t tail_ = 0;     // write position
    LogLine log_buffer_[0xffff + 1]; // relies on the uint16_t overflowing
    volatile uint16_t head_ = 0;     // read position
};

回答1:


Is this logic correct?

Yes.

Is the queue thread safe?

No.

Is volatile sufficient? Is volatile necessary?

No, to both. Volatile is not a magic keyword that makes any variable threadsafe. You still need to use atomic variables or memory barriers for the indexes to ensure memory ordering is correct when you produce or consume an item.

To be more specific, after you produce or consume an item for your queue you need to issue a memory barrier to guarantee that other threads will see the changes. Many atomic libraries will do this for you when you update an atomic variable.

As an aside, use "was_empty" instead of "is_empty" to be clear about what it does. The result of this call is one instance in time which may have changed by the time you act on its value.



来源:https://stackoverflow.com/questions/27139260/spsc-lock-free-queue-without-atomics

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!