Итак, я написал очередь после нескольких исследований. Он использует буфер фиксированного размера, поэтому это круговая очередь. Он должен быть потокобезопасным, и я попытался сделать его незакрепленным. Я хотел бы знать, что с ним не так, потому что подобные вещи трудно предсказать самостоятельно.Ищете критику моей потоковой безопасной реализации без блокировки
Вот заголовок:
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. */
}
}
EDIT: Я сделал некоторые важные изменения в коде, основываясь на комментариях (Спасибо KillianDS и н.м.!). Самое главное, ifront и iback теперь являются ifront1, ifront2, iback1 и iback2. push() теперь увеличит iback1, уведомив другие толки, которые они могут безопасно записать в следующий элемент (пока он не заполнен), напишите элемент, а затем увеличьте iback2. iback2 - это все, что проверяется pop(). pop() делает то же самое, но с индексами ifrontn.
Теперь я снова попадаю в ловушку «Это СЛЕДУЕТ работать ...», но я ничего не знаю о формальных доказательствах или что-то в этом роде. По крайней мере, на этот раз я не могу представить себе потенциальный способ, которым он может потерпеть неудачу. Любые советы приветствуются, за исключением того, что «перестаньте писать код без блокировки».
Сопутствующий контейнер doensn't * имеет * размер и не имеет «полного» или «пустого» состояния. Это бессмысленно. –
@KerrekSB: Круглый буфер * имеет размер, и они часто используются в качестве параллельных контейнеров, поэтому ... –
Сторона примечания. 'if (ifront == iback) return true; else return false; 'in' isEmpty() 'должно быть просто' return ifront == iback; 'то же самое в' isFull() '. Избегайте лишнего if-else. – murrekatt