I want to make mutilple threads in C++ that keep running and waiting for main thread commands and execute them accordingly. Here's the code I wrote and I know it causes the spinning problem. So the question is how can I let the CPU stop running the worker thread untill I change the command. I know there are future and promise, but they seem not suitable for this situation.
[edit] I'm a C++ noob and this is so complicated! If anyone can share me some tutorials or libraries to solve this, that would be much appreciated!
#include <iostream>
#include <thread>
#include <mutex>
class empty
{
public:
empty() {}
void work1()
{
std::cout << "work1" << std::endl;
}
void work2()
{
std::cout << "work2" << std::endl;
}
};
enum CMD
{
WAIT,
CMD1,
CMD2,
DONE
};
void worker(CMD &cmd, empty &e)
{
std::mutex mutex;
while (cmd != DONE)
{
switch (cmd) {
case WAIT:
break;
case CMD1:
e.work1(); // excute cmd 1
mutex.lock();
cmd = WAIT; // change cmd to WAIT
mutex.unlock();
break;
case CMD2:
e.work2();
mutex.lock();
cmd = WAIT;
mutex.unlock();
break;
default:
break;
}
}
}
int main(int argc, const char * argv[]) {
empty e1 = empty();
empty e2 = empty();
CMD cmd = WAIT;
// mutilple thread working on mutilple empty object
std::thread wokerThread1 = std::thread(worker, std::ref(cmd), std::ref(e1));
std::thread wokerThread2 = std::thread(worker, std::ref(cmd), std::ref(e2));
... //some other code
cmd = CMD1;
...
cmd = CMD2;
...
cmd = CMD1;
...
cmd = DONE;
wokerThread1.join();
wokerThread2.join();
return 0;
}
One why of doing this is by using a concurrent_bounded_queue. You can use TBB's implementation for this or implement it using std::queue and std::condition_variable.
Implementation using only std;
#include <queue>
#include <chrono>
#include <thread>
#include <mutex>
#include <iostream>
#include <condition_variable>
std::mutex g_m;
std::condition_variable g_cv;
enum CMD
{
CMD1,
CMD2,
DONE
};
void push_cmd(std::queue<CMD>& tasks, CMD cmd) {
const std::lock_guard<std::mutex> lock(g_m);
tasks.push(cmd);
g_cv.notify_one();
}
CMD pop_cmd(std::queue<CMD>& tasks) {
std::unique_lock<std::mutex> lk(g_m);
g_cv.wait(lk, [&tasks]{ return !tasks.empty(); });
CMD cmd = tasks.front();
tasks.pop();
return cmd;
}
void execute_cmd(int cmd) {
std::cout << std::this_thread::get_id() << ": cmd [" << cmd << "]" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(cmd));
}
void worker(std::queue<CMD>& tasks) {
CMD cmd = pop_cmd(tasks);
while (true)
{
switch (cmd) {
case CMD1:
execute_cmd(1);
break;
case CMD2:
execute_cmd(2);
break;
case DONE:
default:
return;
}
}
}
int main(int argc, const char * argv[]) {
std::queue<CMD> tasks;
std::thread wokerThread1 = std::thread(worker, std::ref(tasks));
std::thread wokerThread2 = std::thread(worker, std::ref(tasks));
push_cmd(tasks, CMD1);
push_cmd(tasks, CMD2);
// push `DONE` for each worker
push_cmd(tasks, DONE);
push_cmd(tasks, DONE);
wokerThread1.join();
wokerThread2.join();
return 0;
}
Implementation using tbb::concurrent_bounded_queue;
#include <tbb/concurrent_queue.h>
void worker(tbb::concurrent_bounded_queue<CMD>& tasks) {
while (true) {
CMD cmd;
tasks.pop(cmd);
switch (cmd) {
case CMD1:
// excute cmd 1
break;
case CMD2:
// excute cmd 2
break;
case DONE:
default:
return;
}
}
}
int main(int argc, const char * argv[]) {
tbb::concurrent_bounded_queue<CMD> tasks;
std::thread wokerThread1 = std::thread(worker, std::ref(tasks));
std::thread wokerThread2 = std::thread(worker, std::ref(tasks));
...
tasks.push(CMD1);
tasks.push(CMD2);
...
}
Note you want to run the same task multiple times you can create a Worker that wraps everything up as follows;
#include <chrono>
#include <queue>
#include <thread>
#include <mutex>
#include <iostream>
#include <condition_variable>
enum CMD
{
CMD1,
CMD2,
DONE
};
void executeCmd(int cmd) {
printf("exec %u: cmd[%d]\n", std::this_thread::get_id(), cmd);
std::this_thread::sleep_for(std::chrono::seconds(cmd));
}
class Worker
{
public:
Worker()
: _thread(std::thread(&Worker::work, this))
{
}
void pushCmd(CMD cmd) {
printf("push %u: cmd[%d]\n", std::this_thread::get_id(), cmd);
const std::lock_guard<std::mutex> lock(_m);
_tasks.push(cmd);
_cv.notify_one();
}
void finish() {
pushCmd(DONE);
_thread.join();
}
private:
std::thread _thread;
std::mutex _m;
std::queue<CMD> _tasks;
std::condition_variable _cv;
CMD popCmd() {
std::unique_lock<std::mutex> lk(_m);
_cv.wait(lk, [&]{ return !_tasks.empty(); });
CMD cmd = _tasks.front();
printf("pop %u: cmd[%d]\n", std::this_thread::get_id(), cmd);
_tasks.pop();
return cmd;
}
void work() {
while (true) {
CMD cmd = popCmd();
switch (cmd) {
case CMD1:
executeCmd(1);
break;
case CMD2:
executeCmd(2);
break;
case DONE:
default:
return;
}
}
}
};
int main(int argc, const char * argv[]) {
Worker w1, w2;
w1.pushCmd(CMD1);
w2.pushCmd(CMD1);
w1.pushCmd(CMD2);
w2.pushCmd(CMD2);
w1.finish();
w2.finish();
return 0;
}
It looks like you could use Observers with async operations here. The idea is that your main control thread updates the CMD for all interested observers and then the observers execute particular operation depending on the CMD. In this example I made "update" operation blocking (previous job must be finished before you start a new one) and returning void. However, you might think about other possibilities, like returning false if previous operation is still ongoing.
#include <chrono>
#include <future>
#include <iostream>
#include <memory>
#include <thread>
#include <vector>
enum CMD
{
WAIT,
CMD1,
CMD2,
DONE
};
class SomeSystem
{
public:
SomeSystem() = default;
void work1()
{
// let's pretend this work takes some time
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::cout << "work1" << std::endl;
}
void work2()
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::cout << "work2" << std::endl;
}
};
class CmdObserver
{
public:
CmdObserver(std::shared_ptr<SomeSystem> system, int id): system_(system), id_(id)
{
std::cout << "observer[" << id_ << "] CTor" << std::endl;
}
void update(CMD cmd)
{
if (work_.valid() && work_.wait_for(std::chrono::seconds(0)) == std::future_status::timeout)
{
std::cout << "observer[" << id_ << "] blocking until previous work is finished" << std::endl;
}
work_ = std::async(std::launch::async, [this, cmd]() { doTheJob(cmd); });
}
private:
void doTheJob(CMD cmd)
{
std::cout << "observer[" << id_ << "] going to execute cmd " << cmd << std::endl;
switch (cmd)
{
case CMD1: system_->work1(); break;
case CMD2: system_->work2(); break;
default: std::cout << cmd << std::endl;
}
}
std::shared_ptr<SomeSystem> system_;
// id_ is just for demonstration purposes
int id_;
std::future<void> work_;
};
int main()
{
int observerId = 0;
std::vector<std::shared_ptr<SomeSystem> > systems({
std::make_shared<SomeSystem>(),
std::make_shared<SomeSystem>(),
std::make_shared<SomeSystem>(),
std::make_shared<SomeSystem>(),
std::make_shared<SomeSystem>()
});
std::vector<CmdObserver> observers;
for (auto system : systems)
{
observers.push_back(CmdObserver(system, observerId));
observerId++;
}
for (auto& observer : observers)
{
observer.update(CMD1);
}
for (auto& observer : observers)
{
observer.update(CMD2);
}
// let's pretend we do some long operation here
std::this_thread::sleep_for(std::chrono::seconds(1));
for (auto& observer : observers)
{
observer.update(CMD1);
}
}
As you mentioned you are a c++ noob, just make sure you are not making any of these mistakes "https://www.acodersjourney.com/top-20-cplusplus-multithreading-mistakes/"
Related
I have the program to count all words in all .log files in given directory using N threads.
I wrote something like this.
ThreadPool.h
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <boost/thread/condition_variable.hpp>
#include <boost/thread.hpp>
#include <future> // I don't how to work with boost future
#include <queue>
#include <vector>
#include <functional>
class ThreadPool
{
public:
using Task = std::function<void()>; // Our task
explicit ThreadPool(int num_threads)
{
start(num_threads);
}
~ThreadPool()
{
stop();
}
template<class T>
auto enqueue(T task)->std::future<decltype(task())>
{
// packaged_task wraps any Callable target
auto wrapper = std::make_shared<std::packaged_task<decltype(task()) ()>>(std::move(task));
{
boost::unique_lock<boost::mutex> lock{ mutex_p };
tasks_p.emplace([=] {
(*wrapper)();
});
}
event_p.notify_one();
return wrapper->get_future();
}
/*void enqueue(Task task)
{
{
boost::unique_lock<boost::mutex> lock { mutex_p };
tasks_p.emplace(std::move(task));
event_p.notify_one();
}
}*/
private:
std::vector<boost::thread> threads_p; // num of threads
std::queue<Task> tasks_p; // Tasks to make
boost::condition_variable event_p;
boost::mutex mutex_p;
bool isStop = false;
void start(int num_threads)
{
for (int i = 0; i < num_threads; ++i)
{
// Add to the end our thread
threads_p.emplace_back([=] {
while (true)
{
// Task to do
Task task;
{
boost::unique_lock<boost::mutex> lock(mutex_p);
event_p.wait(lock, [=] { return isStop || !tasks_p.empty(); });
// If we make all tasks
if (isStop && tasks_p.empty())
break;
// Take new task from queue
task = std::move(tasks_p.front());
tasks_p.pop();
}
// Execute our task
task();
}
});
}
}
void stop() noexcept
{
{
boost::unique_lock<boost::mutex> lock(mutex_p);
isStop = true;
}
event_p.notify_all();
for (auto& thread : threads_p)
{
thread.join();
}
}
};
#endif
main.cpp
#include "ThreadPool.h"
#include <iostream>
#include <iomanip>
#include <Windows.h>
#include <chrono>
#include <vector>
#include <map>
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include <locale.h>
namespace bfs = boost::filesystem;
//int count_words(boost::filesystem::ifstream& file)
//{
// int counter = 0;
// std::string buffer;
// while (file >> buffer)
// {
// ++counter;
// }
//
// return counter;
//}
//
int count_words(boost::filesystem::path filename)
{
boost::filesystem::ifstream ifs(filename);
return std::distance(std::istream_iterator<std::string>(ifs), std::istream_iterator<std::string>());
}
int main(int argc, const char* argv[])
{
std::cin.tie(0);
std::ios_base::sync_with_stdio(false);
bfs::path path = argv[1];
// If this path is exist and if this is dir
if (bfs::exists(path) && bfs::is_directory(path))
{
// Number of threads. Default = 4
int n = (argc == 3 ? atoi(argv[2]) : 4);
ThreadPool pool(n);
// Container to store all filenames and number of words inside them
//std::map<bfs::path, std::future<int>> all_files_and_sums;
std::vector<std::future<int>> futures;
auto start = std::chrono::high_resolution_clock::now();
// Iterate all files in dir
for (auto& p : bfs::directory_iterator(path)) {
// Takes only .txt files
if (p.path().extension() == ".log") {
// Future for taking value from here
auto fut = pool.enqueue([p]() {
// In this lambda function I count all words in file and return this value
int result = count_words(p.path());
static int count = 0;
++count;
std::ostringstream oss;
oss << count << ". TID, " << GetCurrentThreadId() << "\n";
std::cout << oss.str();
return result;
});
// "filename = words in this .txt file"
futures.emplace_back(std::move(fut));
}
}
int result = 0;
for (auto& f : futures)
{
result += f.get();
}
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::seconds>(stop - start);
std::cout << "Result: " << result << "\n";
std::cout << duration.count() << '\n';
}
else
std::perror("Dir is not exist");
}
Variable N is 4(Number of threads). I've 320 .log files in my directory and I need count words in this files. Everything works fine but when variable "count" is 180 - the program stops for a while and then continues but much slower.
What could be the reason?
CPU - Xeon e5430 (I have tested this program on another CPU - the result is the same).
It depends on how you measure "slow" but basically you are using one of the worst models possible:
one task queue shared between all threads.
The problem with this approach is blocking in each thread on the shared queue.
A much better model is something like
task stealing - you can try creating a task queue pro thread and then use try_lock (which doesnt block) with enabling each thread "stealing" work from some other thread's tasks if it has nothing else to do.
This is very nice explained in excellent Sean Parent Talk about Concurrency.
I wrote the program to count all words in .log files in the different threads and output the result on the screen. First argument in command line is dir to find all .log files and then count words in this files. Second argument in command line is number of threads (by default = 4)
I used the ThreadPool for this program
ThreadPool.h
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <boost/thread/condition_variable.hpp>
#include <boost/thread.hpp>
#include <future> // I don't how to work with boost future
#include <queue>
#include <vector>
#include <functional>
class ThreadPool
{
public:
using Task = std::function<void()>; // Our task
explicit ThreadPool(int num_threads)
{
start(num_threads);
}
~ThreadPool()
{
stop();
}
template<class T>
auto enqueue(T task)->std::future<decltype(task())>
{
// packaged_task wraps any Callable target
auto wrapper = std::make_shared<std::packaged_task<decltype(task()) ()>>(std::move(task));
{
boost::unique_lock<boost::mutex> lock{ mutex_p };
tasks_p.emplace([=] {
(*wrapper)();
});
}
event_p.notify_one();
return wrapper->get_future();
}
//void enqueue(Task task)
//{
// {
// boost::unique_lock<boost::mutex> lock { mutex_p };
// tasks_p.emplace(std::move(task));
// event_p.notify_one();
// }
//}
private:
std::vector<boost::thread> threads_p; // num of threads
std::queue<Task> tasks_p; // Tasks to make
boost::condition_variable event_p;
boost::mutex mutex_p;
bool isStop = false;
void start(int num_threads)
{
for (int i = 0; i < num_threads; ++i)
{
// Add to the end our thread
threads_p.emplace_back([=] {
while (true)
{
// Task to do
Task task;
{
boost::unique_lock<boost::mutex> lock(mutex_p);
event_p.wait(lock, [=] { return isStop || !tasks_p.empty(); });
// If we make all tasks
if (isStop && tasks_p.empty())
break;
// Take new task from queue
task = std::move(tasks_p.front());
tasks_p.pop();
}
// Execute our task
task();
}
});
}
}
void stop() noexcept
{
{
boost::unique_lock<boost::mutex> lock(mutex_p);
isStop = true;
event_p.notify_all();
}
for (auto& thread : threads_p)
{
thread.join();
}
}
};
#endif
main.cpp
#include "ThreadPool.h"
#include <iostream>
#include <iomanip>
#include <Windows.h>
#include <vector>
#include <map>
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
namespace bfs = boost::filesystem;
int count_words(const std::string& filename)
{
int counter = 0;
std::ifstream file(filename);
std::string buffer;
while (file >> buffer)
{
++counter;
}
return counter;
}
int main(int argc, const char* argv[])
{
bfs::path path = argv[1];
// If this path is exist and if this is dir
if (bfs::exists(path) && bfs::is_directory(path))
{
// Number of threads. Default = 4
int n = (argc == 3 ? atoi(argv[2]) : 4);
ThreadPool pool(n);
// Container to store all filenames and number of words inside them
std::map<bfs::path, int> all_files_and_sums;
// Iterate all files in dir
for (auto& p : bfs::directory_iterator(path)) {
// Takes only .txt files
if (p.path().extension() == ".log") {
// Future for taking value from here
auto fut = pool.enqueue([&p, &all_files_and_sums]() {
// In this lambda function I count all words in file and return this value
int result = count_words(p.path().string());
std::cout << "TID " << GetCurrentThreadId() << "\n";
return result;
});
// "filename = words in this .txt file"
all_files_and_sums[p.path()] = fut.get();
}
}
int result = 0;
for (auto& k : all_files_and_sums)
{
std::cout << k.first << "- " << k.second << "\n";
result += k.second;
}
std::cout << "Result: " << result << "\n";
}
else
std::perror("Dir is not exist");
}
And this solution works correctly. But if in the directory many files this solution works so slow. I think it's because of the futures. How can I take values from different threads without futures.
(P.S)
Sorry for my english
I've implemented thread pooling following the answer of Kerrek SB in this question.
I've implemented MPMC queue for the functions and vector threads for the threads.
Everything worked perfectly, except that I don't know how to terminate the program, in the end if I just do thread.join since the thread is still waiting for more tasks to do, it will not join and the main thread will not continue.
Any idea how to end the program correctly?
For completeness, this is my code:
function_pool.h
#pragma once
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>
class Function_pool
{
private:
std::queue<std::function<void()>> m_function_queue;
std::mutex m_lock;
std::condition_variable m_data_condition;
public:
Function_pool();
~Function_pool();
void push(std::function<void()> func);
std::function<void()> pop();
};
function_pool.cpp
#include "function_pool.h"
Function_pool::Function_pool() : m_function_queue(), m_lock(), m_data_condition()
{
}
Function_pool::~Function_pool()
{
}
void Function_pool::push(std::function<void()> func)
{
std::unique_lock<std::mutex> lock(m_lock);
m_function_queue.push(func);
// when we send the notification immediately, the consumer will try to
get the lock , so unlock asap
lock.unlock();
m_data_condition.notify_one();
}
std::function<void()> Function_pool::pop()
{
std::unique_lock<std::mutex> lock(m_lock);
m_data_condition.wait(lock, [this]() {return !m_function_queue.empty();
});
auto func = m_function_queue.front();
m_function_queue.pop();
return func;
// Lock will be released
}
main.cpp
#include "function_pool.h"
#include <string>
#include <iostream>
#include <mutex>
#include <functional>
#include <thread>
#include <vector>
Function_pool func_pool;
void example_function()
{
std::cout << "bla" << std::endl;
}
void infinite_loop_func()
{
while (true)
{
std::function<void()> func = func_pool.pop();
func();
}
}
int main()
{
std::cout << "stating operation" << std::endl;
int num_threads = std::thread::hardware_concurrency();
std::cout << "number of threads = " << num_threads << std::endl;
std::vector<std::thread> thread_pool;
for (int i = 0; i < num_threads; i++)
{
thread_pool.push_back(std::thread(infinite_loop_func));
}
//here we should send our functions
func_pool.push(example_function);
for (int i = 0; i < thread_pool.size(); i++)
{
thread_pool.at(i).join();
}
int i;
std::cin >> i;
}
Your problem is located in infinite_loop_func, which is an infinite loop and by result doesn't terminate. I've read the previous answer which suggests throwing an exception, however, I don't like it since exceptions should not be used for the regular control flow.
The best way to solve this is to explicitly deal with the stop condition. For example:
std::atomic<bool> acceptsFunctions;
Adding this to the function pool allows you to clearly have state and to assert that no new functions being added when you destruct.
std::optional<std::function<void()>> Function_pool::pop()
Returning an empty optional (or function in C++14 and before), allows you to deal with an empty queue. You have to, as condition_variable can do spurious wakeups.
With this, m_data_condition.notify_all() can be used to wake all threads.
Finally we have to fix the infinite loop as it doesn't cover overcommitment and at the same time allows you to execute all functions still in the queue:
while (func_pool.acceptsFunctions || func_pool.containsFunctions())
{
auto f = func_pool.pop();
If (!f)
{
func_pool.m_data_condition.wait_for(1s);
continue;
}
auto &function = *f;
function ();
}
I'll leave it up to you to implement containsFunctions() and clean up the code (infinite_loop_func as member function?) Note that with a counter, you could even deal with background task being spawned.
You can always use a specific exception type to signal to infinite_loop_func that it should return...
class quit_worker_exception: public std::exception {};
Then change infinite_loop_func to...
void infinite_loop_func ()
{
while (true) {
std::function<void()> func = func_pool.pop();
try {
func();
}
catch (quit_worker_exception &ex) {
return;
}
}
}
With the above changes you could then use (in main)...
/*
* Enqueue `thread_pool.size()' function objects whose sole job is
* to throw an instance of `quit_worker_exception' when invoked.
*/
for (int i = 0; i < thread_pool.size(); i++)
func_pool.push([](){ throw quit_worker_exception(); });
/*
* Now just wait for each worker to terminate having received its
* quit_worker_exception.
*/
for (int i = 0; i < thread_pool.size(); i++)
thread_pool.at(i).join();
Each instance of infinite_loop_func will dequeue one function object which, when called, throws a quit_worker_exception causing it to return.
Follwoing [JVApen](https://stackoverflow.com/posts/51382714/revisions) suggestion, I copy my code in case anyone will want a working code:
function_pool.h
#pragma once
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <cassert>
class Function_pool
{
private:
std::queue<std::function<void()>> m_function_queue;
std::mutex m_lock;
std::condition_variable m_data_condition;
std::atomic<bool> m_accept_functions;
public:
Function_pool();
~Function_pool();
void push(std::function<void()> func);
void done();
void infinite_loop_func();
};
function_pool.cpp
#include "function_pool.h"
Function_pool::Function_pool() : m_function_queue(), m_lock(), m_data_condition(), m_accept_functions(true)
{
}
Function_pool::~Function_pool()
{
}
void Function_pool::push(std::function<void()> func)
{
std::unique_lock<std::mutex> lock(m_lock);
m_function_queue.push(func);
// when we send the notification immediately, the consumer will try to get the lock , so unlock asap
lock.unlock();
m_data_condition.notify_one();
}
void Function_pool::done()
{
std::unique_lock<std::mutex> lock(m_lock);
m_accept_functions = false;
lock.unlock();
// when we send the notification immediately, the consumer will try to get the lock , so unlock asap
m_data_condition.notify_all();
//notify all waiting threads.
}
void Function_pool::infinite_loop_func()
{
std::function<void()> func;
while (true)
{
{
std::unique_lock<std::mutex> lock(m_lock);
m_data_condition.wait(lock, [this]() {return !m_function_queue.empty() || !m_accept_functions; });
if (!m_accept_functions && m_function_queue.empty())
{
//lock will be release automatically.
//finish the thread loop and let it join in the main thread.
return;
}
func = m_function_queue.front();
m_function_queue.pop();
//release the lock
}
func();
}
}
main.cpp
#include "function_pool.h"
#include <string>
#include <iostream>
#include <mutex>
#include <functional>
#include <thread>
#include <vector>
Function_pool func_pool;
class quit_worker_exception : public std::exception {};
void example_function()
{
std::cout << "bla" << std::endl;
}
int main()
{
std::cout << "stating operation" << std::endl;
int num_threads = std::thread::hardware_concurrency();
std::cout << "number of threads = " << num_threads << std::endl;
std::vector<std::thread> thread_pool;
for (int i = 0; i < num_threads; i++)
{
thread_pool.push_back(std::thread(&Function_pool::infinite_loop_func, &func_pool));
}
//here we should send our functions
for (int i = 0; i < 50; i++)
{
func_pool.push(example_function);
}
func_pool.done();
for (unsigned int i = 0; i < thread_pool.size(); i++)
{
thread_pool.at(i).join();
}
}
I am using an online C++11 compiler, link found here: cpp.sh (C++ Shell).
In my current project, I would like to have a watchdog class, to be able to check somehow the status of a thread or FSM (for example).
After some work (I'm not a C++11 guru), I finally got the code below, that compiles ok.
I also did some basic/trivial tests, but it seems the test program doesn't want to exit.
It says "Program running" and the only way to (force) exit is to hit the "Stop" button... :(
Well, my question : What am I doing wrong?
Any ideas, suggestions you can provide are highly appreciated.
Here is the full code, including my test app:
Watchdog (as MCVE):
#include <thread>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <iostream>
using namespace std::chrono;
class Watchdog
{
public:
Watchdog();
~Watchdog();
void Start(unsigned int milliseconds, std::function<void()> callback = 0);
void Stop();
void Pet();
private:
unsigned int m_interval;
std::atomic<bool> m_running;
std::thread m_thread;
std::function<void()> m_callback;
std::mutex m_mutex;
steady_clock::time_point m_lastPetTime;
std::condition_variable m_stopCondition;
void Loop();
};
Watchdog::Watchdog()
{
m_running = false;
}
Watchdog::~Watchdog()
{
Stop();
}
void Watchdog::Start(unsigned int milliseconds, std::function<void()> callback)
{
std::unique_lock<std::mutex> locker(m_mutex);
if(m_running == false)
{
m_lastPetTime = steady_clock::now();
m_interval = milliseconds;
m_callback = callback;
m_running = true;
m_thread = std::thread(&Watchdog::Loop, this);
}
}
void Watchdog::Stop()
{
std::unique_lock<std::mutex> locker(m_mutex);
if(m_running == true)
{
m_running = false;
m_stopCondition.notify_all();
m_thread.join();
}
}
void Watchdog::Pet()
{
std::unique_lock<std::mutex> locker(m_mutex);
m_lastPetTime = steady_clock::now();
m_stopCondition.notify_all();
}
void Watchdog::Loop()
{
std::unique_lock<std::mutex> locker(m_mutex);
while(m_running == true)
{
if(m_stopCondition.wait_for(locker, milliseconds(m_interval)) == std::cv_status::timeout)
{
if(m_callback != nullptr)
m_callback();
}
}
}
int main(int argc, char *argv[])
{
Watchdog wdog;
wdog.Start(3000, [] { std::cout << " WDOG TRIGGERED!!! "; });
for(auto i = 0; i < 10; i++)
{
std::cout << "[+]";
wdog.Pet();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
-
You're doing a deadlock here.
void Watchdog::Stop()
{
std::unique_lock<std::mutex> locker(m_mutex);
if(m_running == true)
{
m_running = false;
m_stopCondition.notify_all();
m_thread.join();
^ ~~~~~~~~~~~~~~
m_mutex is locked; m_thread cannot continue execution
}
}
Some additional suggestion: use simple if conditions, do not compare with true or false.
I used the following code to create a timer object in my c++ application running on a debian 8.
class Timer
{
private:
std::condition_variable cond_;
std::mutex mutex_;
int duration;
void *params;
public:
Timer::Timer(void (*func)(void*))
{
this->handler = func;
this->duration = 0;
this->params = NULL;
};
Timer::~Timer(){};
void Timer::start(int duree, void* handlerParams)
{
this->duration = duree;
this->params = handlerParams;
/*
* Launch the timer thread and wait it
*/
std::thread([this]{
std::unique_lock<std::mutex> mlock(mutex_);
std::cv_status ret = cond_.wait_for(mlock,
std::chrono::seconds(duration));
if ( ret == std::cv_status::timeout )
{
handler(params);
}
}).detach();
};
void Timer::stop()
{
cond_.notify_all();
}
};
It works correctly under gdb and under normal conditions, but in a load test of 30 requests or more, it crashes with the assertion :
nptl/pthread_mutex_lock.c:350: __pthread_mutex_cond_lock_full: Assertion `(-(e)) != 3 || !robust' failed.
I don't understand the cause of this assertion. Can anyone help me please ??
Thank you
Basically you have a detached thread that accesses the timer object, so it's likely that you destroyed the Timer object but the thread is still running and accessing it's member(mutex, conditional variable).
The assert itself says, from glibc source code, that the owner of the mutex has died.
Thanks a lot for your comments ! I'll try to change the thread detach, and do the load tests.
This is a MVCE of my problem, which is a part of a huge application.
/**
* \file Timer.hxx
* \brief Definition of Timer class.
*/
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
class Timer
{
private:
std::condition_variable cond_;
std::mutex mutex_;
int duration;
void *params;
public:
Timer(void (*func)(void*));
~Timer();
void (*handler)(void*);
void start(int duree, void* handlerParams);
void stop();
};
/*
* Timer.cxx
*/
#include "Timer.hxx"
Timer::Timer(void (*func)(void*))
{
//this->set_handler(func, params);
this->handler = func;
this->duration = 0;
this->params = NULL;
}
Timer::~Timer()
{
}
void Timer::start(int duree, void* handlerParams)
{
this->duration = duree;
this->params = handlerParams;
/*
* Launch the timer thread and wait it
*/
std::thread([this]{
std::unique_lock<std::mutex> mlock(mutex_);
std::cv_status ret = cond_.wait_for(mlock, std::chrono::seconds(duration));
if ( ret == std::cv_status::timeout )
{
handler(params);
}
}).detach();
}
void Timer::stop()
{
cond_.notify_all();
}
/*
* MAIN
*/
#include <stdio.h>
#include <iostream>
#include <unistd.h>
#include "Timer.hxx"
using namespace std;
void timeoutHandler(void* params)
{
char* data= (char*)params;
cout << "Timeout triggered !! Received data is: " ;
if (data!=NULL)
cout << data << endl;
}
int main(int argc, char **argv)
{
int delay=5;
char data[20] ="This is a test" ;
Timer *t= new Timer(&timeoutHandler) ;
t->start(delay, data);
cout << "Timer started !! " << endl;
sleep(1000);
t->stop();
delete t;
cout << "Timer deleted !! " << endl;
return 0;
}