I am trying to learn how to get conditional variables working with some threads, but am having some troubles. The goal is to get the main thread to spawn some 'tasks' for a set period of time which instantiates a Countdown object that includes a thread which uses the timeout of the condition_variable to know that the requested time has indeed lapsed before notifying the caller.
Each time I run the example below it gets a different result, sometimes erroring out, sometimes running, but the timeout for both child threads is shorter than requested (assuming it gets that far).
I appreciate that there are other ways to skin-the-cat to time threads and I could for instance use atomic_x or other means instead of a callback, but I just would like to see how to get the spirit of this implementation (i.e. conditional variable so I don't have to poll).
#include <chrono>
#include <condition_variable>
#include <functional>
#include <iostream>
#include <map>
#include <mutex>
#include <thread>
using Time = std::chrono::system_clock;
using Seconds = std::chrono::seconds;
using Timepoint = Time::time_point;
class Countdown {
private:
Timepoint target;
std::thread t;
std::condition_variable cv;
std::mutex cv_m;
unsigned int guid;
std::string name;
public:
Countdown()
{ // Needed to compile, but doesn't appear to run
std::cout << "empty Countdown constructor" << std::endl;
}
Countdown(unsigned int guid_, std::string name_, unsigned int waitFor, std::function<void(unsigned int)> callback)
: guid(guid_)
, name(name_)
, target(Time::now() + Seconds(waitFor))
{
auto exec_run = [this, guid_, waitFor, callback]() mutable {
std::unique_lock<std::mutex> lk(cv_m);
std::cout << "[Thread " << guid_ << "] waiting for " << waitFor << " seconds." << std::endl;
Timepoint before = Time::now();
if (cv.wait_until(lk, target) == std::cv_status::timeout)
{
Timepoint after = Time::now();
std::chrono::duration<float> difference = after - before;
std::cout << "[Thread " << guid_ << "] Elapsed " << difference.count() << " seconds." << std::endl;
callback(guid_);
}
};
t = std::thread(exec_run);
}
Countdown(Countdown &&from) // move constructor
{
//std::cout << "Countdown move constructor" << std::endl;
target = from.target;
t = std::move(from.t);
name = from.name;
guid = from.guid;
}
~Countdown()
{
//std::cout << "~Countdown()" << std::endl;
if (t.joinable()) t.join();
}
};
class Holder {
private:
std::map<unsigned int, Countdown> waitlist;
unsigned int id;
std::vector<unsigned int> completed;
public:
Holder()
: id(0)
{ }
// Create a new task with a name for WaitFor (s) period of time
unsigned int addTask(std::string name, unsigned int waitFor) {
id++;
waitlist.emplace(std::pair(id, Countdown(id, name, waitFor,
std::bind(&Holder::taskComplete, this, std::placeholders::_1))));
return id;
}
void taskComplete(unsigned int id)
{
std::cout << "[Thread " << id << "] taskComplete" << std::endl;
// Add task id to the completed list to be picked up by main thread
completed.push_back(id);
}
void cleanupCompleted()
{
// Purge the completed entries from the waitlist
for (auto& id : completed)
{
std::cout << "[Main] Erasing task: " << id << std::endl;
waitlist.erase(id);
}
// Empty the completed list
completed.clear();
}
};
int main()
{
Holder *h = new Holder();
// Create a task which spawns a thread, which notifies us when complete
unsigned int id1 = h->addTask("fluffy", 1); // 1 second task
unsigned int id2 = h->addTask("woof", 4); // 4 second task
std::cout << "[Main]: Done adding tasks.." << std::endl;
// Rest a while..
std::this_thread::sleep_for(Seconds(5));
h->cleanupCompleted();
// Just to show the main thread continues on.
std::cout << "[Main]: Doing other stuff.." << std::endl;
delete(h);
return 0;
}
Actual / Expected Result:
[Main]: Done adding tasks..
[Thread 2] waiting for 4 seconds.
[Thread 1] waiting for 1 seconds.
[Thread 2] Elapsed 8.243e-06 seconds. **(This should be ~ 4 seconds)**
[Thread 2] taskComplete
[Thread 1] Elapsed 0.000124505 seconds. **(This should be ~1 second)**
[Thread 1] taskComplete
[Main] Erasing task: 2
[Main] Erasing task: 1
[Main]: Doing other stuff..
I 'think' the problem might be around the in-place addition of the 'Countdown' object to the map in the 'Holder' class. It needs a move constructor whereby I manually set each field (except the mutex and conditional_variable).
Any advice on how to do this correctly?
Aucun commentaire:
Enregistrer un commentaire