多线程应用程序中无锁队列的访问冲突
我根据下面的 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++
I不太清楚可能是什么问题。我的无锁队列的代码如下:
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;
};
我在调试它时遇到了困难,因为读/写偏移量和缓冲区指针从监视窗口看起来很好。不幸的是,当应用程序崩溃时,我无法从 BeginRead 函数中观看自动/局部变量。如果任何人有无锁编程的经验,那么对这个问题的任何帮助或一般建议将不胜感激。
I wrote a simple lockless queue based off of the principles outlined in the msdn article below, and from the DXUT lock free pipe code also below:
So, I have a producer/consumer model setup where my main thread feeds rendering instructions, and an rendering thread consumes available messages and issues the corresponding opengl calls. Things work fine if I sleep my main thread each loop/iteration for a sufficient amount of time, but if I don't sleep it long enough (or not at all), I get an access violation exception:
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.
My call stack is:
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++
I can't quite figure out what the problem might be. The Code for my lockless queue is below:
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;
};
I'm having difficulty debugging it as the read/write offsets and buffer pointer look fine from the watch window. Unfortunately, when the app breaks, I can't watch autos/local variables from the BeginRead function. If anyone has experience working with lockless programming, any help on this problem or advice in general would be greatly appreaciated.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您可能会对这些文章感兴趣...
无锁代码:错误的安全感
编写无锁代码:更正的队列
在第一篇文章中,Herb Sutter 讨论了另一位作者的实现并指出了一些可能出错的地方。在第二篇文章中,赫伯展示了对原始实现的一些更正。
作为学习练习,尝试构建自己的无锁队列是一个非常好的主意。但对于生产工作,您可能会更安全地从可靠的来源找到预先存在的实现并使用它。例如,并发运行时提供concurrent_queue 类
You might find these articles of some interest...
Lock-Free Code: A False Sense of Security
Writing Lock-Free Code: A Corrected Queue
In the first article Herb Sutter discusses another author's implementation of a lock-free queue and points out some of the things that can go wrong. In the second article Herb shows some corrections to the original implementation.
As a learning exercise, trying to build your own lock-free queue is a pretty good idea. But for production work you'd probably be safer finding a pre-existing implementation from a reliable source and using that. For example, the Concurrency Runtime offers the concurrent_queue class
你没有任何记忆栅栏。对易失性变量的访问仅相对于彼此排序,而不是相对于其他操作排序。
在 C++0x 中,您将能够使用
std::atomic
来获取适当的栅栏。在此之前,您将需要特定于操作系统的线程 API,例如 Win32 InterlockedExchange,或包装器库,例如 boost::thread。好的,我看到 AppReadWriteBarrier 应该提供内存栅栏。它是如何实施的?
You haven't any memory fences. Access to volatile variables are only ordered with respect to each other, not to other operations.
In C++0x, you'll be able to use
std::atomic<T>
to get the appropriate fences. Until then you'll need OS-specific threading APIs, such as Win32InterlockedExchange
, or a wrapper library such as boost::thread.Ok, I see that
AppReadWriteBarrier
is supposed to provide a memory fence. How's it implemented?