簡體   English   中英

為什么std :: condition_variable無法正常工作? 總是在等待之前通知

[英]Why std::condition_variable does not work properly? Notify always before wait

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;
};

}

抄送文件

#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();
};
}

抄送文件

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調用notify ,就在這之后我來到了texCond->wait() ...盡管循環器在單獨運行線程...

我究竟做錯了什么?

編輯

問題是-在wait之前調用notify是不可能的。 由於根據實現,我將任務推送到循環程序(任務執行時間為30毫秒),下一行為wait 因此,我將任務推送到單獨的線程->下一行wait ->在30毫秒后notify ... ...但它的工作原理類似於推送任務->在30毫秒后通知-> wait ...怎么可能?

wait()之前調用notify()並非不可能。 使用多個線程時,它們的執行可以隨時啟動和停止。 您的任務執行非常快,因此第一個線程可能在第二個線程完成之前不繼續執行是合理的。

您期望這樣:

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();
}

您需要做的就是將變量添加到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。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM