I have the following project structure: one singleton class (with one processing thread that is waiting in a condition_variable) that is exposed from a dll file and an executable that is referencing the singleton. Through RAII, I am expected that at the destruction of the singleton (and the destruction of the internal implementation) the condition_variable is notified (with a notify_one). This doesn't happen and the behavior is strange: the condition variable is not triggered, but somehow the main thread is joined.
Files from DLL project:
Foo.h:
#pragma once
#ifdef FOODLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#pragma comment(lib,"FooDll.lib")
#endif
#include <thread>
#include <condition_variable>
#include <iostream>
#include <functional>
class Foo_Impl {
public:
Foo_Impl()
:_done(false)
{
_writeThread = std::thread(std::bind(&Foo_Impl::threadRun, this));
}
virtual ~Foo_Impl() {
{
std::unique_lock<std::mutex> lock(_mutex);
_done = true;
}
_cv.notify_one();
if (_writeThread.joinable()) {
_writeThread.join();
}
std::cout << "Joined";
}
void threadRun() {
std::unique_lock<std::mutex> lock(_mutex);
std::cout << "Prepare wait" << std::endl;
_cv.wait(lock, [&]
{
return _done.load();
});
std::cout << "Done thread" << std::endl;
}
std::atomic<bool> _done;
std::thread _writeThread;
std::mutex _mutex;
std::condition_variable _cv;
};
class DLL_API Foo {
private:
Foo_Impl* mp_impl;
public:
static Foo& getInstance() {
static Foo instance;
return instance;
}
private:
Foo()
:mp_impl(new Foo_Impl())
{
}
~Foo()
{
delete mp_impl;
}
Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
};
Foo.cpp
#include "pch.h"
#include "Foo.h"
Files from executable:
main.cpp
#include "../FooDll/Foo.h"
int main()
{
Foo& foo = Foo::getInstance();
std::this_thread::sleep_for(std::chrono::seconds(1)); //allow some time so threadRun is running and waiting
return 0;
}
I am building on VS2019. The output that I am receiving is:
Prepare wait
Joined
If I am not using the Foo class through the Dll, and instead link it with the executable (class DLL_API Foo) the output received is:
Prepare wait
Done thread
Joined
On VS2013 for DLL version I'm getting same output:
Prepare wait
Joined
but for non Dll the thread is never joined:
Prepare wait
Done thread
EDITS : Added predicate to condition_variable and locking the bool guard when it's flagged.
You are using condition variables incorrectly.
_cv.wait(lock)
may wake up at any time with or without a notify. You need to associate a predicate with the condition variable, and either manually wrap it in a loop, or use the predicate overload of wait
:
_cv.wait(lock,[&]{return flag;});
, where flag
is a bool
set by the shutdown code while holding the lock, before notifying:
~Foo_Impl() {
{
std::lock_guard guard(_mutex);
flag=true;
}
_cv.notify_one();
if (_writeThread.joinable()) {
_writeThread.join();
}
std::cout << "Joined";
}
Without doing this, then not only might the wait
not wait, but the notify_one
call might do nothing at all, so the wait
might never wake.