【发布时间】:2014-02-28 22:24:58
【问题描述】:
所以,经过一番研究,我写了一个队列。它使用固定大小的缓冲区,因此它是一个循环队列。它必须是线程安全的,我试图让它无锁。我想知道它有什么问题,因为这些事情我自己很难预测。
这是标题:
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. */
}
}
编辑:我基于 cmets 对代码进行了一些重大修改(感谢 KillianDS 和 n.m.!)。最重要的是,ifront 和 iback 现在是 ifront1、ifront2、iback1 和 iback2。 push() 现在将递增 iback1,通知其他推送线程他们可以安全地写入下一个元素(只要它未满),写入元素,然后递增 iback2。 iback2 是 pop() 检查的所有内容。 pop() 做同样的事情,但使用 ifrontn 索引。
现在,我又一次陷入了“这应该工作......”的陷阱,但我对形式证明或类似的东西一无所知。至少这一次,我想不出它可能会失败的潜在方式。任何建议都值得赞赏,除了“停止尝试编写无锁代码”。
【问题讨论】:
-
并发容器没有有大小,也没有“满”或“空”状态。那些毫无意义。
-
@KerrekSB:循环缓冲区确实有大小,它们通常用作并发容器,所以...
-
@ZanLynx 如果其他线程可以同时更改该状态,那么将大小和“空虚”作为公共接口的一部分是没有意义的。
-
对我来说,您的担忧似乎是有道理的:
push中的compare_and_exchange都通知作者该元素可能不再被写入,而读者该元素可以现在 从中读取。在第一种情况下,不能保证对buffer[i]的原子写入发生在读取之前。 -
"给线程 B 时间" -- 多少时间?做它需要做的就足够了吗?除非您可以填写数字,否则这不是有效的推理方式。坚持“X 发生在 Y 之前”。
标签: c++ multithreading c++11 queue lock-free