Нарушение прав доступа в очереди без блокировки в многопоточном приложении
Я написал простую очередь без блокировки, основанную на принципах, изложенных в статье msdn ниже, а также на коде канала без блокировки DXUT, также ниже:
Итак, у меня есть настройка модели "производитель / потребитель", где мой основной поток передает инструкции по рендерингу, а поток рендеринга потребляет доступные сообщения и выдает соответствующие вызовы opengl. Все работает хорошо, если я сплю мой основной поток каждую петлю / итерацию в течение достаточного количества времени, но если я не сплю достаточно долго (или вообще не сплю), я получаю исключение нарушения доступа:
First-chance exception at 0x00b28d9c in Engine.exe: 0xC0000005: Access violation reading location 0x00004104.
Unhandled exception at 0x777715ee in Engine.exe: 0xC0000005: Access violation reading location 0x00004104.
Мой стек вызовов:
ntdll.dll!777715ee()
[Frames below may be incorrect and/or missing, no symbols loaded for ntdll.dll]
ntdll.dll!777715ee()
ntdll.dll!7776015e()
Engine.exe!RingBuffer<2048>::BeginRead(void * & ppMem=, unsigned long & BytesAvailable=) Line 52 + 0x10 bytes C++
Engine.exe!Thread::ThreadMain(void * lpParam=0x00107d94) Line 41 + 0xf bytes C++
Я не могу понять, в чем проблема. Код моей очереди без блокировки приведен ниже:
template <uint32 BufferSize>
class RingBuffer
{
public:
RingBuffer()
: m_ReadOffset(0)
, m_WriteOffset(0)
{}
~RingBuffer()
{}
bool Empty() const
{
return (m_WriteOffset == m_ReadOffset);
}
void BeginRead(void*& ppMem, uint32& BytesAvailable)
{
const uint32 ReadOffset = m_ReadOffset;
const uint32 WriteOffset = m_WriteOffset;
AppReadWriteBarrier();
const uint32 Slack = (WriteOffset > ReadOffset) ?
(WriteOffset - ReadOffset) :
(ReadOffset > WriteOffset) ?
(c_BufferSize - ReadOffset) :
(0);
ppMem = (m_Buffer + ReadOffset);
BytesAvailable = Slack;
}
void EndRead(const uint32 BytesRead)
{
uint32 ReadOffset = m_ReadOffset;
AppReadWriteBarrier();
ReadOffset += BytesRead;
ReadOffset %= c_BufferSize;
m_ReadOffset = ReadOffset;
}
void BeginWrite(void*& ppMem, uint32& BytesAvailable)
{
const uint32 ReadOffset = m_ReadOffset;
const uint32 WriteOffset = m_WriteOffset;
AppReadWriteBarrier();
const uint32 Slack = (WriteOffset > ReadOffset || WriteOffset == ReadOffset) ?
(c_BufferSize - WriteOffset) :
(ReadOffset - WriteOffset);
ppMem = (m_Buffer + WriteOffset);
BytesAvailable = Slack;
}
void EndWrite(const uint32 BytesWritten)
{
uint32 WriteOffset = m_WriteOffset;
AppReadWriteBarrier();
WriteOffset += BytesWritten;
WriteOffset %= c_BufferSize;
m_WriteOffset = WriteOffset;
}
private:
const static uint32 c_BufferSize = NEXT_POWER_OF_2(BufferSize);
const static uint32 c_SizeMask = c_BufferSize - 1;
private:
byte8 m_Buffer[ c_BufferSize ];
volatile ALIGNMENT(4) uint32 m_ReadOffset;
volatile ALIGNMENT(4) uint32 m_WriteOffset;
};
У меня возникают трудности при отладке, так как смещения чтения / записи и указатель буфера выглядят нормально из окна просмотра. К сожалению, когда приложение ломается, я не могу смотреть auto / local переменные из функции BeginRead. Если у кого-то есть опыт работы с программированием без блокировки, любая помощь по этой проблеме или советы в целом будут очень важны.
2 ответа
Вы могли бы найти эти статьи некоторый интерес...
Код без блокировки: ложное чувство безопасности
Написание кода без блокировки: исправленная очередь
В первой статье Херб Саттер обсуждает реализацию очереди без блокировки другого автора и указывает на некоторые вещи, которые могут пойти не так. Во второй статье Херб показывает некоторые исправления в оригинальной реализации.
В качестве учебного упражнения попытка создать собственную очередь без блокировки является довольно хорошей идеей. Но для производственной работы вам, вероятно, будет безопаснее найти уже существующую реализацию из надежного источника и использовать ее. Например, среда выполнения с параллелизмом предлагает класс concurrent_queue
У вас нет никаких заборов памяти. Доступ к переменным переменным упорядочен только по отношению друг к другу, а не к другим операциям.
В C++0x вы сможете использовать std::atomic<T>
чтобы получить соответствующие заборы. До этого вам понадобятся специфичные для ОС API-интерфейсы потоков, такие как Win32 InterlockedExchange
или библиотека-оболочка, такая как boost::thread.
Хорошо я вижу что AppReadWriteBarrier
должен обеспечить забор памяти. Как это реализовано?