【发布时间】:2015-01-28 21:06:57
【问题描述】:
我的问题是这样的:
我有一个观察者,它持有一个 std::condition_variable 和一个 std::mutex,我的工作线程对象有一个指向观察者的指针。每次工作线程完成其工作时,它都会调用 m_Observer->NotifyOne(),然后调用 condition_variable 的 notify_one() 函数。现在我想要做的是,启动一堆工作线程,每个线程都有不同的工作和不同的(独立)数据,并等待所有线程向观察者发出信号(使用 m_Observer->NotifyOne()),以便我能够根据所有工作线程的结果继续工作。
我的观察者看起来像这样:
class IAsyncObserver
{
private:
std::condition_variable m_ObserverCV;
bool m_bNotified;
std::mutex m_Mutex;
public:
IAsyncObserver()
{
m_bNotified = false;
}
~IAsyncObserver()
{
/*m_bNotified = true;
m_ObserverCV.notify_all();*/
}
void NotifyOne()
{
std::unique_lock<std::mutex> Lock(m_Mutex);
m_bNotified = true;
m_ObserverCV.notify_one();
}
void NotifyAll()
{
std::unique_lock<std::mutex> Lock(m_Mutex);
m_bNotified = true;
m_ObserverCV.notify_all();
}
void WaitForNotifications(uint32_t _uNumOfNotifications = 1)
{
uint32_t uNotifyCount = 0;
while (uNotifyCount < _uNumOfNotifications)
{
std::unique_lock<std::mutex> Lock(m_Mutex);
m_bNotified = false;
m_ObserverCV.wait(Lock);
if (m_bNotified)
++uNotifyCount;
}
}
}; // IAsyncObserver
其中 _uNumOfNotifications 是我要等待的工作线程数。
现在每个工作线程都应该运行一个模拟函数,该函数为一个时间步长/数据垃圾执行实际工作,然后暂停/等待,直到观察者通知工作线程继续。
worker 的线程函数可能如下所示:
do{
//suspend simulation
while (m_PauseSimulation.load())
{
std::unique_lock<std::mutex> wait(m_WaitMutex);
m_CV.wait(wait);
if (m_RunSimulation.load() == false)
{
SignalObserver();
return;
}
}
//lock the data while simulating
{
std::lock_guard<std::mutex> lock(m_LockMutex);
//update simulation
Simulate(static_cast<float>(m_fDeltaTime));
m_PauseSimulation.store(true);
}
//notify the physics manager that work is done here
SignalObserver();
} while (m_RunSimulation.load());
SignalObserver() 只是调用 m_Observer->NotifyOne()。
现在的问题是,一段时间后,线程在某处陷入死锁,并且观察者没有通知它们继续下一个时间步。问题可能出在 WaitForNotifications() 函数中,但我不确定。 Atm 我只有一个工作线程,所以 uNumOfNotifications = 1,但它仍然遇到在 m_ObserverCV.wait(Lock) 和 m_CV.wait(wait) 等待的问题,我什至不确定它是否真的是死锁或什么使用 condition_variable,因为我尝试从多个线程访问它。
在这一点上,我想引用 Ned Flanders 父亲的话:“我们什么都没尝试,而且都没有想法!”
感谢您的帮助。任何小费表示赞赏。
法比安
编辑:
感谢所有有用的信息和建议。我最终实现了迈克尔的第二个想法,因为我没有找到任何关于 std::barriers 的信息。所以这就是我所做的:
class IAsyncObserver
{
private:
std::condition_variable m_ObserverCV;
bool m_bNotified;
std::mutex m_Mutex;
uint32_t m_uNumOfNotifications;
uint32_t m_uNotificationCount;
public:
IAsyncObserver()
{
m_bNotified = false;
m_uNumOfNotifications = 0;
m_uNotificationCount = 0;
}
~IAsyncObserver()
{
/*m_bNotified = true;
m_ObserverCV.notify_all();*/
}
void SetBarrier(uint32_t _uNumOfNotifications = 1)
{
m_uNumOfNotifications = _uNumOfNotifications;
}
void NotifyBarrier()
{
std::unique_lock<std::mutex> Lock(m_Mutex);
if (++m_uNotificationCount >= m_uNumOfNotifications)
{
m_bNotified = true;
m_ObserverCV.notify_one();
}
}
void WaitForNotifications()
{
std::unique_lock<std::mutex> Lock(m_Mutex);
while (m_bNotified == false)
{
m_ObserverCV.wait(Lock);
}
m_uNotificationCount = 0;
}
void NotifyOne()
{
std::unique_lock<std::mutex> Lock(m_Mutex);
m_bNotified = true;
m_ObserverCV.notify_one();
}
void NotifyAll()
{
std::unique_lock<std::mutex> Lock(m_Mutex);
m_bNotified = true;
m_ObserverCV.notify_all();
}
}; // IAsyncObserver
在我的“主要”功能中:MassSpringSystem 和 RigidBodySystem 是我的工人 atm
//update systems here:
{
SetBarrier(m_uTotalNotifyCount);
{ //start MassSpringSystems
std::lock_guard<std::mutex> lock(m_LockMutex);
for (std::shared_ptr<MassSpringSystem> MSS : m_MassSpringSystems)
{
MSS->SetDeltaTime(fDeltaTime);
MSS->Continue();
}
}
//ATTENTION this system works directly on the m_OctreeEntities!
{ //start RigidBodySystems
m_RigidBodySystem.SetDeltaTime(fDeltaTime);
m_RigidBodySystem.AddData(m_RigidBodies);
m_RigidBodySystem.Continue();
}
//wait for all systems to finish -> till they call SignalObserver
WaitForNotifications();
}
和上面一样在worker的线程函数中,但是这次SignalObserver调用了NotifyBarrier()
现在一切正常。一个简单但功能强大的解决方案,谢谢!
【问题讨论】:
-
可能还有更多问题,但您不需要互斥锁来保护您的条件变量。它们是线程安全的。
-
您的观察者等待循环看起来很奇怪。在等待观察者 CV 之前,您锁定互斥锁,然后将通知状态硬设置为 false。除非在工人开始之前最初设置了该状态,否则您将参加比赛,看看谁可以锁定互斥锁并首先硬设置状态。 IE。 (1) worker 锁定 mutex,(2) worker 通过 NotifyOne 将 state 设置为 true,(3)observer 进入 wait-loop 并等待 mutex,(4) worker 释放 mutex,(5)observer 获得 mutex 并将通知状态擦除到false,(6) 观察者开始等待谓词变化。
-
另外,如果您需要更具体的帮助,请提供一个能够重现您观察到的行为的最低工作程序。无法重现问题,只能根据对代码的观察进行推测。
标签: c++ multithreading wait condition-variable