0

我根据下面 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;
    };

我很难调试它,因为从监视窗口看读/写偏移量和缓冲区指针看起来很好。不幸的是,当应用程序中断时,我无法从 BeginRead 函数中查看自动/局部变量。如果有人有使用无锁编程的经验,那么对于这个问题的任何帮助或一般的建议都会非常感激。

4

2 回答 2

2

您可能会发现这些文章有些有趣...

无锁代码:一种错误的安全感
编写无锁代码:更正的队列

在第一篇文章中,Herb Sutter 讨论了另一位作者对无锁队列的实现,并指出了一些可能出错的地方。在第二篇文章中,Herb 展示了对原始实现的一些更正。

作为一项学习练习,尝试构建自己的无锁队列是一个不错的主意。但是对于生产工作,您可能会更安全地从可靠来源找到预先存在的实现并使用它。例如,并发运行时提供了concurrent_queue

于 2011-08-08T00:29:48.383 回答
1

你没有任何记忆栅栏。对 volatile 变量的访问仅相对于彼此进行排序,而不是相对于其他操作。

在 C++0x 中,您将能够使用std::atomic<T>来获得适当的栅栏。在此之前,您将需要特定于操作系统的线程 API,例如 Win32InterlockedExchange或诸如 boost::thread 的包装库。

好的,我看到它AppReadWriteBarrier应该提供一个内存围栏。它是如何实施的?

于 2011-08-07T23:53:36.990 回答