Windows上的多线程c ++ 11-ish队列失败

时间:2014-07-24 07:36:29

标签: c++ multithreading c++11 mutex condition-variable

我不是那么多线程,所以我很感激任何建议 在我的服务器中,这是用生产者 - 消费者多线程风格写的 queue与其mutexcv

完全包装在一起
template <typename Event>
struct EventsHandle {
public: // methods:
    Event*
    getEvent()
    {
            std::unique_lock<std::mutex> lock {mutex};
            while (events.empty()) {
                    condition_variable.wait(lock);
            }
            return events.front();
    };

    void
    setEvent(Event* event)
    {
            std::lock_guard<std::mutex> lock {mutex};
            events.push(event);
            condition_variable.notify_one();
    };

    void
    pop()
    { events.pop(); };

private: // fields:
    std::queue<Event*> events;
    std::mutex mutex;
    std::condition_variable condition_variable;
};

以及如何在消费者线程中使用它:

void
Server::listenEvents()
{
    while (true) {
            processEvent(events_handle.getEvent());
            events_handle.pop();
    }
};

并且在制片人中:

    parse input, whatever else
    ...
    setEvent(new Event {ERASE_CLIENT, getSocket(), nullptr});
    ...

void
Client::setEvent(Event* event)
{
    if (event) {
            events_handle->setEvent(event);
    }
};

代码适用于linux,我不知道为什么,但在Windows MSVC13上失败 在某些时候,此对话框会抛出异常:
"Unhandled exception at 0x59432564 (msvcp120d.dll) in Server.exe: 0xC0000005: Access violation reading location 0xCDCDCDE1".
调试显示此行上出现异常:std::lock_guard<std::mutex> lock(mutex)函数中的setEvent()

小小的谷歌搜索让我看到这些文章:
http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html
http://www.codeproject.com/Articles/598695/Cplusplus-threads-locks-and-condition-variables

我试着跟着他们,但没什么帮助。所以在这篇长篇文章之后我的问题: 代码有什么问题,互斥?

更新
所以...最后在可爱的游戏名为“评论出线”之后,事实证明问题出在内存管理上。事件的执行者导致失败 作为结论,最好使用std :: unique_ptr&lt;&gt;提到Jarod42或在前后传递对象时使用值语义作为建议juanchopanza 尽可能使用库,不要重新发明轮=)

3 个答案:

答案 0 :(得分:2)

由于EventsHandle::pop中缺少互斥锁,您进行了数据竞争。

您的生产者线程可以通过调用setEvent()将项目推送到队列,并在执行行events.push(event)时被抢占。现在,消费者线程可以同时执行events.pop()。最终在queue上有两个未同步的写操作,这是未定义的行为。

另请注意,如果您有多个消费者,则需要确保您弹出的元素与之前从getEvent检索到的元素相同。如果一个消费者在两个电话之间被另一个消费者抢占。使用两个独立的成员函数很难实现这一点,这些函数由作为类成员的互斥锁同步。这里通常的方法是提供单个getEventAndPop()函数,在整个操作过程中保持锁定并摆脱当前的单独函数。这看起来似乎是一个荒谬的限制,但多线程代码必须遵循不同的规则。

答案 1 :(得分:1)

您可能还希望在互斥锁仍处于锁定状态时将setEvent方法更改为不通知。它取决于调度程序,但等待通知的线程可能会立即唤醒,只是等待互斥锁。

void 
setEvent(Event* event) 
{
    {
        std::lock_guard<std::mutex> lock{mutex};
        events.push(event);
    }
    condition_variable.notify_one();
};

答案 2 :(得分:0)

在我看来,pop应该集成在getEvent()中。

这将阻止更多线程获得相同的事件(如果pop不在getEvent中,那么更多的线程可以获得相同的事件)。

Event*
getEvent()
{
    std::unique_lock<std::mutex> lock {mutex};
    while (events.empty()) {
            condition_variable.wait(lock);
    }
    Event* front = events.front();
    events.pop();
    return front;
};