[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

posted @ 2024-06-27 11:41  围城chen  阅读(8)  评论(0编辑  收藏  举报