为什么std :: condition_variable无法正常工作?总是在等待之前通知

时间:2019-07-15 11:37:16

标签: c++ multithreading condition-variable

void VideoRender::execute(){
    for(int i = 0; i < 1000; i++) 
       udpateData(myPath, myIndex);
}

void VideoRender::updateData(const std::string &i_obj_path, const uint i_subIndex)
{
    std::shared_ptr<FrameManager> container = std::make_shared<FrameManager>(m_nativeCodec);

    std::thread th1(&VideoRender::loadGeometry, this, i_obj_path.c_str(), i_subIndex, container);
    std::thread th2(&VideoRender::loadTextures, this, container);

    th1.join();
    th2.join();

    m_fifo.enqueue(container);
}

这里的问题是,每次调用updateData方法时,都会创建2个新线程。

因此,我决定将此逻辑更改为使用std::condition_variable

我做了什么

1)创建了ConditionEvent

h.file

namespace hello_ar
{
class ConditionEvent
{
public:
    ConditionEvent() = default;

    ~ConditionEvent() = default;

    void wait();

    void notify();

private:
    mutable std::mutex m_mutex;
    std::condition_variable m_condition;
};

}

cc文件

#include "ConditionEvent.h"
#include <android/log.h>

namespace hello_ar
{
void ConditionEvent::wait()
{
    std::unique_lock<std::mutex> lock(m_mutex);
    m_condition.wait(lock);
}

void ConditionEvent::notify()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    m_condition.notify_all();
}
}

然后我创建了一个UploadLooper

h文件

namespace hello_ar
{
class UploadLooper
{
public:
    UploadLooper() = default;

    ~UploadLooper();

    void start();

    void enqueueLoadTextureTask(const std::shared_ptr<FrameManager> &container, std::shared_ptr<ConditionEvent> condition);

    void enqueueLoadGeometryTask(const std::shared_ptr<FrameManager> &container, char const *filename, const uint subIndex, 
std::shared_ptr<ConditionEvent> condition);

    void quit();

private:
    enum class Task
    {
        LoadGeometry, LoadTexture, ERROR
    };

    struct LooperMessage
    {
        std::shared_ptr<FrameManager> m_container;
        std::shared_ptr<ConditionEvent> m_condition;
        char const *m_filename;
        uint m_subIndex;
        Task m_task;

        //Load Geometry
        LooperMessage(std::shared_ptr<FrameManager> container, //
                      std::shared_ptr<ConditionEvent> condition,//
                      char const *filename = "", //
                      const uint subIndex = static_cast<const uint>(-1),//
                      Task task = Task::ERROR //
        ) : m_container(container), m_condition(condition), 
 m_filename(filename), m_subIndex(subIndex), m_task(task)
        {
        }

        //Load Textures
        LooperMessage(std::shared_ptr<FrameManager> container, //
                      std::shared_ptr<ConditionEvent> condition,//
                      Task task = Task::ERROR//
        ) : LooperMessage(container, //
                          condition,
                          "", //
                          static_cast<uint>(-1), //
                          task//
        )
        {
        }
    };

    safe_queue<std::shared_ptr<LooperMessage>> m_fifo;
    std::thread m_worker;

    void loop();

    void trampoline(void *p);

    void releaseWorker();
};
}

cc文件

namespace hello_ar
{
UploadLooper::~UploadLooper()
{
    quit();
}

void UploadLooper::releaseWorker()
{
    if (m_worker.joinable())
        m_worker.join();
}

void UploadLooper::trampoline(void *p)
{
    ((UploadLooper *) p)->loop();
}

void UploadLooper::loop()
{
    while (true)
    {
        if (m_fifo.empty())
            continue;

        std::shared_ptr<LooperMessage> msg = m_fifo.dequeue();

        if (!msg)
        {
            return;
        }

        switch (msg->m_task)
        {
            case Task::LoadGeometry:
            {
                msg->m_container->LoadFrameData(msg->m_filename, msg->m_subIndex);
                msg->m_condition->notify();
            }
                break;

            case Task::LoadTexture:
            {
                msg->m_container->LoadImage();
                msg->m_condition->notify();
            }
                break;
            case Task::ERROR:
                break;
        }

        std::this_thread::yield();
    }
}

void UploadLooper::enqueueLoadTextureTask(const std::shared_ptr<FrameManager> &container, std::shared_ptr<ConditionEvent> condition)
{
    std::shared_ptr<LooperMessage> msg = std::make_shared<LooperMessage>(container, condition, Task::LoadTexture);
    m_fifo.enqueue(msg);
}

void UploadLooper::enqueueLoadGeometryTask(const std::shared_ptr<FrameManager> &container, //
                                           char const *filename, const uint subIndex, //
                                           std::shared_ptr<ConditionEvent> condition)
{
    std::shared_ptr<LooperMessage> msg = std::make_shared<LooperMessage>(container, condition, filename, subIndex, Task::LoadGeometry);
    m_fifo.enqueue(msg);
}

void UploadLooper::quit()
{
    m_fifo.enqueue(nullptr);
    releaseWorker();
}

void UploadLooper::start()
{
    if (!m_worker.joinable())
    {
        std::thread t(&UploadLooper::trampoline, this, this);
        m_worker = std::move(t);
    }
}
}

最终我的乞求实现看起来像这样

void VideoRender::execute(){
    for(int i = 0; i < 1000; i++) 
       udpateData(myPath, myIndex);
}

void VideoRender::updateData(const std::string &i_obj_path, const uint i_subIndex)
{
    std::shared_ptr<FrameManager> container = std::make_shared<FrameManager>(m_nativeCodec);

    std::shared_ptr<ConditionEvent> texCond = std::make_shared<ConditionEvent>();
    std::shared_ptr<ConditionEvent> geoCond = std::make_shared<ConditionEvent>();

    m_texLopper.enqueueLoadTextureTask(container, texCond);
    m_geometryLopper.enqueueLoadGeometryTask(container, i_obj_path.c_str(), i_subIndex, geoCond);

    texCond->wait();
    geoCond->wait();

    m_fifo.enqueue(container);
}

但是在调试之后,我发现第一次调用updateData方法之后,我来到m_texLopper调用notify,然后来到m_geometryLooper调用{{1} },然后我来到notify ...尽管循环程序在单独的线程中运行...

我在做什么错了?

编辑

问题是-无法在texCond->wait()之前调用notify。由于根据实现,我将任务推送到循环程序(任务执行时间为30毫秒),下一行为wait。因此,我将任务推入单独的线程-> 30毫秒后wait->下一行wait ...但它的工作原理类似于推任务-> 30毫秒后通知-> notify ...怎么可能?

2 个答案:

答案 0 :(得分:2)

notify()之前调用wait()并非不可能。使用多个线程时,它们的执行可以随时启动和停止。您的任务执行得非常快,因此第一个线程可能在第二个线程完成之前不继续执行是合理的。

您期望这样:

Thread 1             Thread 2
enqueue
wait                 dequeue
                     LoadFrameData()
                     notify

但这也是可能的:

Thread 1             Thread 2
enqueue
                     dequeue
                     LoadFrameData()
                     notify
wait

无论何时通知您的条件变量,您都应添加条件以进行检查。整个代码可以这样简化:

class ConditionEvent {
public:
    void ConditionEvent::wait() {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_condition.wait(lock, [&]() {return notified;});
    }

    void ConditionEvent::set() {
        std::lock_guard<std::mutex> lock(m_mutex);
        notified = true;
        m_condition.notify_all();
    }

    void reset() {
        notified = false;
    }

private:
    mutable std::mutex m_mutex;
    bool notified = false;
    std::condition_variable m_condition;
};


void VideoRender::execute() {
    std::shared_ptr<FrameManager> container;
    ConditionEvent geometry, textures;

    // If this thread obtains the lock, initialize the container
    auto init = [&]() {
        std::lock_guard<std::mutex> lock(containerMutex);
        if (!container) {
            container = std::make_shared<FrameManager>(m_nativeCodec);
        }
    };

    // If this thread obtains the lock, enqueue the container
    auto enqueue = [&]() {
        std::lock_guard<std::mutex> lock(containerMutex);
        if (container) {
            m_fifo.enqueue(container);
            container.reset();
            geometry.reset();
            textures.reset();           
        }
    };

    std::thread t1([]() {
        for (int i = 0; i < 1000; i++) {
            init();     
            loadGeometry();
            geometry.set();
            textures.wait();
            enqueue();            
        }
    });
    std::thread t2([]() {
        for (int i = 0; i < 1000; i++) {
            init();
            loadTextures();
            textures.set();
            geometry.wait();
            enqueue();
        }
    });

    t1.join();
    t2.join();
}

答案 1 :(得分:2)

您需要做的就是向ConditionEvent添加变量,即

bool notified = false;

然后使用此变量:

void ConditionEvent::wait()
{
    std::unique_lock<std::mutex> lock(m_mutex);
    m_condition.wait(lock, [this]() {return notified;});
}

void ConditionEvent::notify()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    notified = true;
    m_condition.notify_all();
}

编辑:固定了lambda。