[Effective Modern C++] 条款39笔记 - 条件变量的notify与wait相关问题分析
背景
当存在一个主线程和一个子线程,主线程负责将数据放入一个无锁队列中,并通知子线程从队列中取出数据,进行处理。
通常的做法是,通过条件变量,主线程调用notify_one,而子线程在wait处阻塞,当收到notify_one通知时,子线程被唤醒开始处理。
// 主线程
bool FileMonitorService::pushMsgToQueue(uint8_t* pBuffer, uint32_t size)
{
if (!queue_.push(pBuffer))
{
LOG_WRN<<"File monitor receive queue is full.";
return false;
}
cv.notify_one();
return true;
}
// 子线程函数
void FileMonitorService::handleMessage()
{
uint8_t* pBuffer = nullptr;
while(serv_start)
{
{
boost::unique_lock lk(mtx);
cv.wait(lk);
}
while(queue_.pop(pBuffer))
{
// parse data
}
}
}
事情看起来这么的美好。然而,需要注意的是,由下面引用的文章中指出的,notify_one通知并不会存入堆栈,如果子线程此时不处于wait状态,则这个通知就无效了。例如,当子线程处理完数据,即跳出while(queue_.pop(pBuffer))循环,准备再次进入cv.wait(lk);之前,主线程调用cv.notify_one();此时通知即无效。
除此之外,《Effective Modern C++》的条款39中也指出,还有如下问题:
• 本例存在代码异味,异味源是使用了互斥体,一般互斥体是用千控制共享数据访问的。由于notify不需要用锁,而仅仅在子线程中使用了。
• wait语句无法应对虚假唤醒。
解决方法
那么上述方法该如何处理呢,针对本案例,一个合适的方法是使用一个普通的flag。具体代码如下:
主线程:
std::condition_variable cv; // as before
std::mutex m;
bool flag(false); // not std::atomic
… // detect event
{
std::lock_guard<std::mutex> g(m); // lock m via g's ctor
flag = true; // tell reacting task
// (part 1)
} // unlock m via g's dtor
cv.notify_one(); // tell reacting task
// (part 2)
子线程:
… // prepare to react
{ // as before
std::unique_lock<std::mutex> lk(m); // as before
cv.wait(lk, [] { return flag; }); // use lambda to avoid spurious wakeups(虚假唤醒)
flag=false; //我加的
… // react to event
// (m is locked)
}
… // continue reacting
// (m now unlocked)
上述条件变量和标志位一起使用的通信机制,虽然设计结果不甚自然,但至少能解决问题。异曲同工的是,还可以使用一个count计数,代替flag。代码如下:
namespace
{
Semaphore::Semaphore()
{
m_count = 0;
}
Semaphore::Semaphore(unsigned int count)
{
m_count = count;
}
void Semaphore::Notify()
{
boost::lock_guard<boost::mutex> lock(m_mutex);
if (m_count != UINT32_MAX)
{
m_count++;
}
m_cv.notify_one();
}
void Semaphore::Wait()
{
boost::unique_lock<boost::mutex> lock(m_mutex);
while (m_count == 0) {
m_cv.wait(lock);
}
m_count--;
}
bool Semaphore::TryWait(boost::chrono::milliseconds timeout)
{
bool bret = false;
boost::unique_lock<boost::mutex> lock(m_mutex);
if (m_count == 0)
{
boost::cv_status status = m_cv.wait_for(lock, timeout);
if (boost::cv_status::no_timeout == status)
{
if (m_count > 0)
{
m_count--;
}
bret = true;
}
}
else
{
m_count--;
bret = true;
}
return bret;
}
}
引用:
https://stackoverflow.com/questions/14582505/boost-condition-variables-do-calls-to-notify-one-stack