多线程 c++11-ish 队列在 Windows 上失败
Posted
技术标签:
【中文标题】多线程 c++11-ish 队列在 Windows 上失败【英文标题】:Multithreaded c++11-ish queue fails on windows 【发布时间】:2014-07-24 07:36:28 【问题描述】:我不太喜欢多线程,所以我很感激任何建议。
在我以生产者-消费者多线程样式编写的服务器中queue
与其mutex
和cv
一起包装:
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".
调试显示在此行上引发了异常:setEvent()
函数中的std::lock_guard<std::mutex> lock(mutex)
。
通过谷歌搜索,我找到了这些文章:http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.htmlhttp://www.codeproject.com/Articles/598695/Cplusplus-threads-locks-and-condition-variables
我试图跟随他们,但没有得到什么帮助。所以在这个长文本表之后我的问题是: 代码有什么问题,互斥锁?
更新 所以...最后,在名为“comment that line out”的可爱游戏之后,结果发现问题出在内存管理上。事件的 dctor 导致失败。 作为结论,在来回传递对象时,最好使用 std::unique_ptr 作为提及 Jarod42 或使用值语义作为建议 juanchopanza。 并尽可能使用库,不要重新发明*** =)
【问题讨论】:
你有一个消费者吗?否则,您应该分别对getEvent()
和pop()
进行同步访问,即将它们放在队列中的一个单一同步方法中。另外(对于自我广告感到抱歉),我实现了一个概念验证,极其简单和愚蠢的并发队列here。底部有一个指向源代码的 github 链接。看看它是否适合你会很有趣。
@juanchopanza 我刚刚尝试将 pop() 移动到 getEvent() ,例如:'Event* tmp = events.front();事件.pop();返回 tmp;没有改变。不,我有很多制作人。
注意:“消费者”之所以被称为“消费者”,是因为它会消费一些东西。那东西不能留给其他人以后尝试消费。
我会使用std::unique_ptr<Event>
而不是原始的Event*
,这样可以避免内存管理和悬空指针。
@Aenry 好吧,当生产者添加事件并使队列重新分配时,指向消费者持有的事件的指针可能会失效。你不应该真的返回指针。使用值语义。
【参考方案1】:
由于EventsHandle::pop
中缺少互斥体,您存在数据竞争。
您的生产者线程可以通过调用setEvent()
将项目推入队列,并在执行events.push(event)
行时被抢占。现在消费者线程可以同时执行events.pop()
。您最终会在 queue
上进行两次不同步的写入,这是未定义的行为。
另请注意,如果您有多个消费者,则需要确保您弹出的元素与您之前从getEvent
检索到的元素相同。如果一个消费者在两个调用之间被另一个消费者抢占。这很难通过两个独立的成员函数来实现,这两个成员函数由作为该类成员的互斥锁同步。这里通常的方法是提供一个getEventAndPop()
函数,它在整个操作过程中保持锁定并摆脱您当前拥有的单独函数。乍一看,这似乎是一个荒谬的限制,但多线程代码必须遵循不同的规则。
【讨论】:
即使使用同步的pop()
,由于队列返回指向数据的指针,而不是值,一个线程调用pop()
可能会给另一个线程留下悬空指针。
@juanchopanza 仅当一个线程被允许弹出另一个检索到的元素时,我的最后一段希望解决这个问题。
但是你怎么能保证呢?这是一个队列。你消费和弹出任何发生在前面的东西。
@juanchopanza 或者通过外部同步(这很丑陋并且在某种程度上破坏了线程安全队列的概念),或者通过更改接口以提供retrieveAndPop
。
没错。这就是我试图提出的观点。两个调用在并发队列的接口中不能分开。【参考方案2】:
您可能还想将setEvent
方法更改为在互斥锁仍处于锁定状态时不通知。这取决于调度程序,但等待通知的线程可能会立即唤醒以等待互斥锁。
void
setEvent(Event* event)
std::lock_guard<std::mutex> lockmutex;
events.push(event);
condition_variable.notify_one();
;
【讨论】:
除非你必须关心通知消费者的优先级反转。见this page for details。另请注意,许多现代实现实际上足够智能,如果相应的锁尚未释放,则不会唤醒在条件变量上阻塞的线程。 有趣。谢谢你的链接!【参考方案3】:在我看来,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;
;
【讨论】:
以上是关于多线程 c++11-ish 队列在 Windows 上失败的主要内容,如果未能解决你的问题,请参考以下文章