I have this code for a custom class 'sau_timer':
sau_timer::sau_timer(int secs, timerparam f, vector<string> params) : strnd(io),
t(io, boost::posix_time::seconds(secs))
{
assert(secs > 0);
this->f = f;
this->params = params;
t.async_wait(strnd.wrap(boost::bind(&sau_timer::exec, this, _1)));
boost::thread thrd(boost::bind(&boost::asio::io_service::run, &io));
io.run();
}
void sau_timer::exec(const boost::system::error_code&) {
(f)(params);
}
I want it so that when I make a sau_timer object, the timer will start, but allow program execution to continue. For example, this is main():
int main(int argc, char* argv[])
{
vector<string> args(1);
args[0] = "Hello!";
sau_timer timer_test(3, sau_prompt, args);
args[0] = "First!";
sau_prompt(args);
timer_test.thrd.join();
return 0;
}
My intention here is that timer_test is made, starting a timer that waits three seconds before calling sau_prompt("Hello!"), but that sau_prompt("First!") will be called first. At the moment, Hello is shown in the prompt before First, indicating that the timer is halting the entire program for three seconds before allowing it to continue. I want the timer to run in the background.
What am I doing wrong? The code compiles...
Thank you.
You're calling "io.run()" in sau_timer - that essentially tells the asio reactor to process any/all pending async events if it can.
You should call run or post after the having setup the events, which is how its normally done. check out the examples in the asio documentation.
#include <iostream>
#include <asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/date_time.hpp>
#include <boost/thread.hpp>
class event_timer
{
public:
event_timer(asio::io_service& io_service,
const std::size_t& tid,
const std::size_t& interval = 5)
: io_service_(io_service),
timer_(io_service),
tid_(tid),
interval_(interval),
tick_count_(0),
total_diff_(0)
{
}
void start()
{
timer_.cancel();
initiate_timer();
}
void stop()
{
timer_.cancel();
}
void set_interval(const std::size_t& milliseconds)
{
interval_ = milliseconds;
}
private:
inline void initiate_timer()
{
if (interval_)
{
timer_.expires_from_now(boost::posix_time::milliseconds(interval_));
timer_.async_wait(
boost::bind(&event_timer::handle_timer_event,this,
asio::placeholders::error));
before_ = boost::posix_time::microsec_clock::universal_time();
}
}
inline void handle_timer_event(const asio::error_code& error)
{
if (!error && interval_)
{
after_ = boost::posix_time::microsec_clock::universal_time();
boost::posix_time::time_duration duration = after_ - before_;
total_diff_ += std::abs(interval_ - duration.total_milliseconds());
++tick_count_;
if (tick_count_ < 200)
initiate_timer();
else
std::cout << "Timer["<< tid_ <<"]\tTick["<< tick_count_ <<"] Average Diff: " << total_diff_ / (1.0 * tick_count_) << std::endl;
}
}
asio::io_service& io_service_;
std::size_t tid_;
std::size_t interval_;
std::size_t tick_count_;
asio::deadline_timer timer_;
boost::posix_time::ptime before_;
boost::posix_time::ptime after_;
std::size_t total_diff_;
};
int main()
{
std::cout << "Timer Test" << std::endl;
asio::io_service io_service;
try
{
const std::size_t et_cnt = 1000;
std::vector<event_timer*> et_lst;
for(unsigned int i = 0; i < et_cnt; ++i)
{
et_lst.push_back(new event_timer(io_service,i,10));
}
for(unsigned int i = 0; i < et_cnt;)
{
et_lst[i++]->start();
}
std::size_t thread_pool_size = 100;
//Create a pool of threads to run all of the io_services.
std::vector<boost::shared_ptr<boost::thread> > threads;
for (std::size_t i = 0; i < thread_pool_size; ++i)
{
boost::shared_ptr<boost::thread> thread(new boost::thread(boost::bind(&asio::io_service::run, &io_service)));
threads.push_back(thread);
}
// Wait for all threads in the pool to exit.
for (std::size_t i = 0; i < threads.size(); ++i)
threads[i]->join();
for(unsigned int i = 0; i < et_cnt; delete et_lst[i++]);
}
catch(std::exception& e)
{
std::cout << "Exception: " << e.what() << std::endl;
return 1;
}
return 0;
}
considering namespace and boost version 1.69, there are three modifications should be done:
change #include <asio.hpp> into #include <boost/asio.hpp>
add: using namespace boost; using namespace boost:asio;
change inline void handle_timer_event(const asio::error_code& error) into void handle_timer_event(const boost::system::error_code& error)
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();
}
}
My question is, if I run io_service::run () on multiple threads, do I need to implement blocking on these asynchronous functions?
example:
int i = 0;
int j = 0;
void test_timer(boost::system::error_code ec)
{
//I need to lock up here ?
if (i++ == 10)
{
j = i * 10;
}
timer.expires_at(timer.expires_at() + boost::posix_time::milliseconds(500));
timer.async_wait(&test_timer);
}
void threadMain()
{
io_service.run();
}
int main()
{
boost::thread_group workers;
timer.async_wait(&test_timer);
for (int i = 0; i < 5; i++){
workers.create_thread(&threadMain);
}
io_service.run();
workers.join_all();
return 0;
}
The definition of async is that it is non-blocking.
If you mean to ask "do I have to synchronize access to shared objects from different threads" - that question is unrelated and the answer depends on the thread-safety documented for the object you are sharing.
For Asio, basically (rough summary) you need to synchronize concurrent access (concurrent as in: from multiple threads) to all types except boost::asio::io_context¹,².
Your Sample
Your sample uses multiple threads running the io service, meaning handlers run on any of those threads. This means that effectively you're sharing the globals and indeed they need protection.
However Because your application logic (the async call chain) dictates that only one operation is ever pending, and the next async operation on the shared timer object is always scheduled from within that chain, the access is logically all from a single thread (called an implicit strand. See Why do I need strand per connection when using boost::asio?
The simplest thing that would work:
Logical Strand
Live On Coliru
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <iostream>
boost::asio::io_service io_service;
boost::asio::deadline_timer timer { io_service };
struct state_t {
int i = 0;
int j = 0;
} state;
void test_timer(boost::system::error_code ec)
{
if (ec != boost::asio::error::operation_aborted) {
{
if (state.i++ == 10) {
state.j = state.i * 10;
if (state.j > 100)
return; // stop after 5 seconds
}
}
timer.expires_at(timer.expires_at() + boost::posix_time::milliseconds(50));
timer.async_wait(&test_timer);
}
}
int main()
{
boost::thread_group workers;
timer.expires_from_now(boost::posix_time::milliseconds(50));
timer.async_wait(&test_timer);
for (int i = 0; i < 5; i++){
workers.create_thread([] { io_service.run(); });
}
workers.join_all();
std::cout << "i = " << state.i << std::endl;
std::cout << "j = " << state.j << std::endl;
}
Note I removed the io_service::run() from the main thread as it is redundant with the join() (unless you really wanted 6 threads running the handlers, not 5).
Prints
i = 11
j = 110
Caveat
There's a pitfall lurking here. Say, you didn't want to bail at a fixed number, like I did, but want to stop, you'd be tempted to do:
timer.cancel();
from main. That's not legal, because the deadline_timer object is not thread safe. You'd need to either
use a global atomic_bool to signal the request for termination
post the timer.cancel() on the same strand as the timer async chain. However, there is only an explicit strand, so you can't do it without changing the code to use an explicit strand.
More Timers
Let's complicate things by having two timers, with their own implicit strands. This means access to the timer instances still need not be synchronized, but access to i and j does need to be.
Note In this demo I use synchronized_value<> for elegance. You can write similar logic manually using mutex and lock_guard.
Live On Coliru
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/thread/synchronized_value.hpp>
#include <iostream>
boost::asio::io_service io_service;
struct state {
int i = 0;
int j = 0;
};
boost::synchronized_value<state> shared_state;
struct TimerChain {
boost::asio::deadline_timer _timer;
TimerChain() : _timer{io_service} {
_timer.expires_from_now(boost::posix_time::milliseconds(50));
resume();
}
void resume() {
_timer.async_wait(boost::bind(&TimerChain::test_timer, this, _1));
};
void test_timer(boost::system::error_code ec)
{
if (ec != boost::asio::error::operation_aborted) {
{
auto state = shared_state.synchronize();
if (state->i++ == 10) {
state->j = state->i * 10;
}
if (state->j > 100) return; // stop after some iterations
}
_timer.expires_at(_timer.expires_at() + boost::posix_time::milliseconds(50));
resume();
}
}
};
int main()
{
boost::thread_group workers;
TimerChain timer1;
TimerChain timer2;
for (int i = 0; i < 5; i++){
workers.create_thread([] { io_service.run(); });
}
workers.join_all();
auto state = shared_state.synchronize();
std::cout << "i = " << state->i << std::endl;
std::cout << "j = " << state->j << std::endl;
}
Prints
i = 12
j = 110
Adding The Explicit Strands
Now it's pretty straight-forward to add them:
struct TimerChain {
boost::asio::io_service::strand _strand;
boost::asio::deadline_timer _timer;
TimerChain() : _strand{io_service}, _timer{io_service} {
_timer.expires_from_now(boost::posix_time::milliseconds(50));
resume();
}
void resume() {
_timer.async_wait(_strand.wrap(boost::bind(&TimerChain::test_timer, this, _1)));
};
void stop() { // thread safe
_strand.post([this] { _timer.cancel(); });
}
// ...
Live On Coliru
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/thread/synchronized_value.hpp>
#include <iostream>
boost::asio::io_service io_service;
struct state {
int i = 0;
int j = 0;
};
boost::synchronized_value<state> shared_state;
struct TimerChain {
boost::asio::io_service::strand _strand;
boost::asio::deadline_timer _timer;
TimerChain() : _strand{io_service}, _timer{io_service} {
_timer.expires_from_now(boost::posix_time::milliseconds(50));
resume();
}
void resume() {
_timer.async_wait(_strand.wrap(boost::bind(&TimerChain::test_timer, this, _1)));
};
void stop() { // thread safe
_strand.post([this] { _timer.cancel(); });
}
void test_timer(boost::system::error_code ec)
{
if (ec != boost::asio::error::operation_aborted) {
{
auto state = shared_state.synchronize();
if (state->i++ == 10) {
state->j = state->i * 10;
}
}
// continue indefinitely
_timer.expires_at(_timer.expires_at() + boost::posix_time::milliseconds(50));
resume();
}
}
};
int main()
{
boost::thread_group workers;
TimerChain timer1;
TimerChain timer2;
for (int i = 0; i < 5; i++){
workers.create_thread([] { io_service.run(); });
}
boost::this_thread::sleep_for(boost::chrono::seconds(10));
timer1.stop();
timer2.stop();
workers.join_all();
auto state = shared_state.synchronize();
std::cout << "i = " << state->i << std::endl;
std::cout << "j = " << state->j << std::endl;
}
Prints
i = 400
j = 110
¹ (or using the legacy name boost::asio::io_service)
² lifetime mutations are not considered member operations in this respect (you have to manually synchronize construction/destruction of shared objects even for thread-safe objects)
I have a test class like this. What I want to do is to keep running the three timers in this object. But after I instantiate an object, some timer just keeps repeating but others will disappear after like 3 mins. Can anyone explain this for me?
class EventProcessor
{
private:
boost::asio::deadline_timer* m_Timer0;
boost::asio::deadline_timer* m_Timer1;
boost::asio::deadline_timer* m_Timer2;
boost::asio::io_service io0;
boost::asio::io_service io1;
boost::asio::io_service io2;
int TimerInterval[3];
boost::asio::deadline_timer* Timers[3];
public:
EventProcessor(int p_0, int p_1, int p_2)
{
TimerInterval[0] = p_0;
TimerInterval[1] = p_1;
TimerInterval[2] = p_2;
m_Timer0= new boost::asio::deadline_timer(io0, boost::posix_time::seconds(TimerInterval[0]));
Timers[0] = m_Timer0;
m_Timer1 = new boost::asio::deadline_timer(io1, boost::posix_time::seconds(TimerInterval[1]));
Timers[1] = m_Timer1;
m_Timer2 = new boost::asio::deadline_timer(io2, boost::posix_time::seconds(TimerInterval[2]));
Timers[2] = m_Timer2;
m_Timer0->async_wait(boost::bind(&EventProcessor::HandleExpire, this, boost::asio::placeholders::error, 0));
m_Timer1->async_wait(boost::bind(&EventProcessor::HandleExpire, this, boost::asio::placeholders::error, 1));
m_Timer2->async_wait(boost::bind(&EventProcessor::HandleExpire, this, boost::asio::placeholders::error, 2));
StartWithNewThread(0);
StartWithNewThread(1);
StartWithNewThread(2);
}
private:
void HandleExpire(const boost::system::error_code& p_ec, int p_TimerIndex)
{
if(p_ec == boost::asio::error::operation_aborted)
{
std::cout << "Timer" << p_TimerIndex << " canceled" << std::endl;
return;
}
std::cout << "Timer" << p_TimerIndex << " expired" << std::endl;
//Reset(p_OriginalTimer, TimerInterval[p_TimerIndex], p_TimerIndex);
boost::thread Thread(boost::bind(&EventProcessor::Reset, this, p_TimerIndex, TimerInterval[p_TimerIndex]));
}
void Start(int p_Index)
{
boost::asio::io_service& UnderlyingIO = Timers[p_Index]->get_io_service();
UnderlyingIO.reset();
UnderlyingIO.run();
UnderlyingIO.stop();
return;
}
void StartWithNewThread(int p_Index)
{
boost::thread Thread(boost::bind(&EventProcessor::Start, this, p_Index));
std::cout << Thread.get_id() << "<->" << "Timer" << p_Index << std::endl;
return;
}
public:
void Reset(int p_Index, int p_Seconds)
{
Timers[p_Index]->cancel();
Timers[p_Index]->expires_from_now(boost::posix_time::time_duration(0,0,p_Seconds,0));
TimerInterval[p_Index] = p_Seconds;
Timers[p_Index]->async_wait(boost::bind(&EventProcessor::HandleExpire, this, boost::asio::placeholders::error, p_Index));
boost::asio::io_service& UnderlyingIO = Timers[p_Index]->get_io_service();
UnderlyingIO.reset();
UnderlyingIO.run();
UnderlyingIO.stop();
return;
}
};
So this is how you should do it:
#include "test.h"
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/atomic.hpp>
class EventProcessor
{
private:
std::unique_ptr<boost::asio::deadline_timer> m_Timers[3];
boost::asio::io_service service;
boost::atomic<int> TimerInterval[3];
public:
EventProcessor(int time0,int time1, int time2)
{
TimerInterval[0] = time0;
TimerInterval[1] = time1;
TimerInterval[2] = time2;
for (int i = 0; i < 3; i++)
{
m_Timers[i].reset(
new boost::asio::deadline_timer(service));
}
}
~EventProcessor()
{
service.stop();
for (int i = 0; i < 3; i++)
{
m_Timers[i]->cancel();
}
}
void Run()
{
for (int i = 0; i < 3; i++)
{
m_Timers[i]->expires_from_now(boost::posix_time::seconds(TimerInterval[i]));
m_Timers[i]->async_wait(boost::bind(&EventProcessor::HandleExpire,
this,
i,
_1));
}
service.run();
}
void RunAsync()
{
boost::thread(boost::bind(&EventProcessor::Run,this));
}
void Reset(int i,int seconds)
{
TimerInterval[i] = seconds;
m_Timers[i]->expires_from_now(boost::posix_time::seconds(TimerInterval[i]));
m_Timers[i]->async_wait(boost::bind(&EventProcessor::HandleExpire,
this,
i,
_1));
}
private:
void HandleExpire(int p_TimerIndex, const boost::system::error_code& error)
{
if(error == boost::asio::error::operation_aborted)
{
std::cout << "Timer" << p_TimerIndex << " canceled" << std::endl;
return;
}
std::cout << "Timer" << p_TimerIndex << " expired" << std::endl;
//Reset(p_OriginalTimer, TimerInterval[p_TimerIndex], p_TimerIndex);
m_Timers[p_TimerIndex]->expires_from_now(
boost::posix_time::seconds(TimerInterval[p_TimerIndex]));
m_Timers[p_TimerIndex]->async_wait(boost::bind(&EventProcessor::HandleExpire,
this,
p_TimerIndex,
_1));
}
};
int main()
{
EventProcessor ev(1,2,3);
ev.RunAsync();
getchar();
ev.Reset(2,4);
getchar();
}
Granted I don't have any of the fancy checkers to see if you are currently running or not (which you totally need if you want this to be safe to use).
You can think of boost::asio::io_service as a context in which async calls can be made. It creates a FIFO queue of messages to process, and processes them where and when you tell it to. The most common way to process these messages is boost::asio::io_service::run, which will process messages until there is nothing left to be done. "nothing left to be done" is a flexible definition: it doesn't necessarily mean there is a message to process, just that there is stuff to be done. Things like a deadline timer make sure that there is "something to be done" as long as an async_wait is going on until the handler is called. You can manually enforce that there is something to be done by creating a boost::asio::io_service::work instance. This makes it so that there is "something left to be done" for the lifetime of the work object.
The deadline timer class takes care of all the async calls for you, so you don't have to spawn all those threads. The io_service performs synchronization, which is necessary to prevent annoying control issues.
So to the problem with your code:
With all those threads controlling the io_service, it is hard to tell what is actually going wrong...I have to guess on what could possibly going wrong. I'd put my money on somewhere along the line, you call a io_service::cancel before a deadline timer times out, which will stop your loop. I solve this in my code by doing all the control (calling wait_async) in one synchronous thread (the io_service::run call) and only calling io_service::cancel when I want the code to stop.