I'm implementing something of a thread janitor in modern C++. What I've got works, and while it's not organized in the way I'd like yet, I'd like some feedback on the fundamentals. Thank you in advance for any and all pointers. =)
All of the threads (including main()
in order to signal program end) share data via this:
#ifndef SHAREDDATA_HPP
#define SHAREDDATA_HPP
#include <condition_variable>
#include <thread>
class SharedData {
public:
std::mutex keypress_mutex;
std::condition_variable keypress_cv;
bool keypress_flag;
std::mutex thread_count_mutex;
std::condition_variable thread_count_cv;
unsigned int thread_count;
std::mutex thread_kill_mutex;
std::condition_variable thread_kill_cv;
bool thread_kill_flag;
SharedData():
keypress_flag{false},
thread_count{0},
thread_kill_flag{false}
{ }
~SharedData() = default;
};
#endif // SHAREDDATA_HPP
Then we've got a little RAII guy to help keep track of thread count:
#ifndef THREADCOUNTER_HPP
#define THREADCOUNTER_HPP
#include <thread>
class SharedData;
class ThreadCounter {
public:
explicit ThreadCounter(SharedData *share);
~ThreadCounter();
private:
SharedData *_share;
};
#endif // THREADCOUNTER_HPP
//--------------------------
#include "ThreadCounter.hpp"
#include "SharedData.hpp"
ThreadCounter::ThreadCounter(SharedData *share) :
_share{share}
{
std::lock_guard<std::mutex> _lock(_share->thread_count_mutex);
share->thread_count++;
}
ThreadCounter::~ThreadCounter() {
std::lock_guard<std::mutex> _lock(_share->thread_count_mutex);
_share->thread_count--;
if(_share->thread_count == 0) {
_share->thread_count_cv.notify_one();
}
}
Next, the ManagedThread
class itself:
#ifndef MANAGEDTHREAD_HPP
#define MANAGEDTHREAD_HPP
#include <thread>
class SharedData;
class ManagedThread {
public:
void launch();
explicit ManagedThread(SharedData *share);
~ManagedThread();
ManagedThread() = delete;
void operator()();
private:
SharedData *_share;
std::thread _thread;
};
#endif // MANAGEDTHREAD_HPP
//--------------------------
#include "ManagedThread.hpp"
#include "SharedData.hpp"
#include "ThreadCounter.hpp"
#include <chrono>
#include <iostream>
#include <cassert>
using namespace std::chrono_literals;
void ManagedThread::launch() {
if(!_thread.joinable()) {
_thread = std::thread(std::ref(*this));
}
else {
assert((false) && "Managed Thread not launchable!");
}
}
ManagedThread::ManagedThread(SharedData *share) :
_share{share},
_thread{std::thread()}
{ }
ManagedThread::~ManagedThread() {
if(_thread.joinable()) {
_thread.join();
}
}
void ManagedThread::operator()() {
ThreadCounter tc(_share);
while(true) {
std::this_thread::sleep_for(250ms);
std::cout << "Whee!" << std::endl;
std::unique_lock<std::mutex> kill_lock(_share->thread_kill_mutex);
if(_share->thread_kill_cv.wait_for(kill_lock, 100ms,
[&](){ return _share->thread_kill_flag; })) {
break;
}
}
}
And ThreadManager
who does the cleanup coordination:
#ifndef THREADMANAGER_HPP
#define THREADMANAGER_HPP
class SharedData;
class ThreadManager {
public:
explicit ThreadManager(SharedData *share) : _share{share} { };
~ThreadManager() = default;
void operator()();
private:
SharedData *_share;
};
#endif // THREADMANAGER_HPP
//--------------------------
#include "ThreadManager.hpp"
#include "SharedData.hpp"
#include <chrono>
using namespace std::chrono_literals;
void ThreadManager::operator()() {
std::this_thread::sleep_for(25ms);
std::unique_lock<std::mutex> keypress_lock(_share->keypress_mutex);
_share->keypress_cv.wait(keypress_lock,
[&](){ return _share->keypress_flag; });
keypress_lock.unlock();
{
std::lock_guard<std::mutex> kill_lock(_share->thread_kill_mutex);
_share->thread_kill_flag = true;
}
_share->thread_kill_cv.notify_all();
std::unique_lock<std::mutex> count_lock(_share->thread_count_mutex);
_share->thread_count_cv.wait(count_lock,
[&](){ return _share->thread_count == 0; });
}
And, finally, a main.cpp
to tie it all together.
#include "SharedData.hpp"
#include "ThreadManager.hpp"
#include "ManagedThread.hpp"
#include <iostream>
#include <future>
int main() {
SharedData share;
ThreadManager tm(&share);
ManagedThread t0(&share);
t0.launch();
std::future<void> cf = std::async(std::launch::async, std::move(tm));
int input;
std::cin >> input;
{
std::lock_guard<std::mutex> lock(share.keypress_mutex);
share.keypress_flag = true;
}
share.keypress_cv.notify_one();
cf.get();
return 0;
}
What I know I don't really like is that ThreadManager
doesn't own SharedData
. I'd also like ThreadManager
to generically manage any ManagedThread
and perhaps overload operator()
in an inherited thread of some sort so callable can do anything but still be "managed".
At this point, those are just musings, though - what I'd really like to know is how well or poorly the system has been implemented thus far. =)
Again, thanks for your feedback; I look forward to improving this setup and utilizing it in future.