寻找对我的线程安全,无锁队列实施的批评

Looking for critique of my thread safe, lock-free queue implementation

本文关键字:队列 我的 线程 安全 寻找      更新时间:2023-10-16

所以,经过一些研究,我已经写了一个队列。它使用固定尺寸的缓冲区,因此是圆形队列。它必须是线程安全的,我试图使其无锁。我想知道这是什么问题,因为这些事情很难自己预测。

这是标题:

template <class T>
class LockFreeQueue
{
public:
    LockFreeQueue(uint buffersize) : buffer(NULL), ifront1(0), ifront2(0), iback1(0), iback2(0), size(buffersize) { buffer = new atomic <T>[buffersize]; }
    ~LockFreeQueue(void) { if (buffer) delete[] buffer; }
    bool pop(T* output);
    bool push(T input);
private:
    uint incr(const uint val)
        {return (val + 1) % size;}
    atomic <T>* buffer;
    atomic <uint> ifront1, ifront2, iback1, iback2;
    uint size;
};

这是实现:

template <class T>
bool LockFreeQueue<T>::pop(T* output)
{
    while (true)
    {
        /* Fetch ifront and store it in i. */
        uint i = ifront1;
        /* If ifront == iback, the queue is empty. */
        if (i == iback2)
            return false;
        /* If i still equals ifront, increment ifront, */
        /* Incrememnting ifront1 notifies pop() that it can read the next element. */
        if (ifront1.compare_exchange_weak(i, incr(i)))
        {
            /* then fetch the output. */
            *output = buffer[i];
            /* Incrememnting ifront2 notifies push() that it's safe to write. */
            ++ifront2;
            return true;
        }
        /* If i no longer equals ifront, we loop around and try again. */
    }
}
template <class T>
bool LockFreeQueue<T>::push(T input)
{
    while (true)
    {
        /* Fetch iback and store it in i. */
        uint i = iback1;
        /* If ifront == (iback +1), the queue is full. */
        if (ifront2 == incr(i))
            return false;
        /* If i still equals iback, increment iback, */
        /* Incrememnting iback1 notifies push() that it can write a new element. */
        if (iback1.compare_exchange_weak(i, incr(i)))
        {
            /* then store the input. */
            buffer[i] = input;
            /* Incrementing iback2 notifies pop() that it's safe to read. */
            ++iback2;
            return true;
        }
        /* If i no longer equals iback, we loop around and try again. */
    }
}

编辑:根据评论,我对代码进行了一些重大修改(感谢Killiands和N.M.!)。最重要的是,Ifront和Iback现在是IFRONT1,IFRONT2,IBACK1和IBACK2。push()现在将增加iback1,并通知其他推动线程可以安全地写入下一个元素(只要它不满),写入元素,然后递增iback2。iback2是由pop()检查的全部。pop()做同样的事情,但是使用ifrontn索引。

现在,我再次陷入了"这应该起作用……"的陷阱,但我对正式证明或类似的事情一无所知。至少这一次,我想不出一种可能失败的潜在方法。除了"停止尝试编写免费锁定代码"外,任何建议都将受到赞赏。

接近锁定数据结构的正确方法是编写一个半正式证明您的设计在伪代码中起作用。您不应该问"此锁定代码线程安全是安全的",而是"我的锁定代码是线程安全有任何错误的证明?"

只有在您有正式证明伪代码设计工作后,您才尝试实施它。通常,这会带来诸如垃圾收集之类的轻度问题,这些问题必须小心处理。

您的代码应在评论中是正式的证明和伪代码,其中相对不重要的实现插入了内部。

验证您的代码是否正确,然后包括了解伪代码,检查证明,然后检查是否失败了您的代码以映射到伪代码和证明。

直接采用代码并试图检查其锁定是否不切实际。证明是正确设计这种事情的重要事项,实际代码是次要的,因为证明是困难的部分。

之后一个盲点,有一个洞,或者不了解您的并发基础,或者您的并发原语中有错误。

如果您对编写半正式证明来设计代码不感兴趣,则不应是手滚动锁定的免费算法和数据结构并将其放置在生产代码中。

确定一堆代码"是线程安全"是否将所有工作负担限制给其他人。您需要有一个论点,为什么您的代码"是线程安全"以使其他人尽可能容易地找到其中的孔的方式安排了"线程安全"。如果您的论点为何要以更难找到孔的方式安排您的代码"是线程安全",那么您的代码也不能假定是安全的,即使没有人可以在您的代码中发现一个漏洞。

您上面发布的代码是一团糟。它包含评论的代码,没有正式的不变式,没有证据表明线条,没有强烈的描述它是线程安全的,并且一般不会提出试图以一种易于发现的方式表明自己作为线程安全的尝试缺陷。因此,没有合理的读者将考虑代码线程安全,即使他们找不到任何错误。

no,它不是线程安全 - 如果事件:

,请考虑以下顺序
  1. 第一个线程在pop中完成if (ifront.compare_exchange_weak(i, incr(i))),然后按调度程序入睡。
  2. 第二个线程呼叫按size次(足以使IFRONT等于第一个线程中的i值)。
  3. 第一个线程唤醒。

在这种情况下,POP buffer[i]将包含最后一个推动值,这是错误的。

考虑包裹环绕时存在一些问题,但我认为您的代码的主要问题是它可能会从缓冲区中弹出无效的值。

考虑一下:

ifront = iback = 0

推动被调用,CAS增加了iback 0-> 1的值。但是,在分配缓冲区[0]之前,该线程现在停滞不前。

ifront = 0,iback = 1

现在调用了pop。在分配之前,请阅读CAS增加IFRONT 1-> 1和缓冲区[0]。

弹出了陈旧或无效的值。

ps。因此,一些研究要求使用DCA或TCA(DI和TRI CAS)。