![](/img/trans.png)
[英]Calling std::condition_variable::notify_one() multiple times before std::condition_variable::wait() is called
[英]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.