多线程c++11-ish队列在windows上失败

Multithreaded c++11-ish queue fails on windows

本文关键字:失败 windows c++11-ish 队列 多线程      更新时间:2023-10-16

我不太喜欢多线程,所以我很感谢你的建议。
在我的服务器这是写在生产者-消费者多线程风格
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();
    }
};

和producer:

    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".
调试显示在setEvent()函数的std::lock_guard<std::mutex> lock(mutex)行抛出异常。

我用谷歌搜索了一下这些文章:
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

我试着跟着他们,但是没有什么帮助。在这篇长文之后我的问题是代码怎么了,互斥锁?


所以…最后,在可爱的游戏名为"评论那行",原来,问题是在内存管理。事件的执行器导致失败。
作为结论,在来回传递对象时,最好像Jarod42提到的那样使用std::unique_ptr<>,或者像juanchopanza建议的那样使用值语义。
尽可能使用库,不要重新发明轮子=)

由于EventsHandle::pop缺少互斥锁,导致数据竞争。

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

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

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

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

我认为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;
};