Search code examples
c++multithreadingcondition-variablestdthread

notify_one not triggering condition_variable inside dynamic library


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.


Solution

  • 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.