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.
Related
Current Scheme
I am developing a Serial Port routine that will regard current receive transfer is complete if no new data is received for 25 milli-seconds. I start the timer on the first the read_handler (Boost ASIO callback method) call. For every new read_handler call, I cancel the asynchronous operations that are waiting on the timer and create a new asynchronous operations on the timer.
Problem
The problem I am facing is that randomly my receive transfer that was suppose to be 1 transfer is being treated as 2 separate transfer as receive_timeout event (receive_timeout_handler) is being triggered (called) multiple times.
I'm not sure is this because of my incorrect implementation/usage of Boost ASIO system_timer or due to Driver issue in my USB to Serial Converter.
I'm currently using FT4232 module (contains 4 UART/Serial Port) to test my routines whereby I send data from send data (4 K.B. text file) from UART1 and receive data on UART0.
I expect that only after receiving all 4 K.B. of data, the serial port class signal main thread however sometimes this one 4 K.B. transfer is signaled 2-3 times.
Code :
class SerialPort
{
public:
SerialPort() : io(), port(io), receive_timeout_timer(io)
bool open_port(void);
bool read_async(std::int32_t read_timeout = -1)
void read_handler(const boost::system::error_code& error, std::size_t bytes_transferred);
void receive_timeout_handler(const boost::system::error_code& error);
private:
boost::asio::io_context io;
boost::asio::serial_port port;
boost::asio::system_timer receive_timeout_timer {25};
std::array<std::byte, 8096> read_byte_buffer;
};
bool SerialPort::open_port(void)
{
try
{
this->port.open("COM3");
return true;
}
catch (const std::exception& ex)
{
}
return false;
}
bool SerialPort::read_async(std::uint32_t read_timeout)
{
try
{
this->read_byte_buffer.fill(static_cast<std::byte>(0)); //Clear Buffer
if (read_timeout not_eq -1)
{
this->read_timeout = read_timeout;//If read_timeout is not set to ignore_timeout, update the read_timeout else use old read_timeout
}
this->port.async_read_some(
boost::asio::buffer(
this->read_byte_buffer.data(),
this->read_byte_buffer.size()
),
boost::bind(
&SerialPort::read_handler,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
return true;
}
catch (const std::exception& ex)
{
return false;
}
}
void SerialPort::read_handler(const boost::system::error_code& error, std::size_t bytes_transferred)
{
std::string temporary_recieve_data;
try
{
if (error not_eq boost::system::errc::success) //Error in serial port read
{
return;
}
std::transform(this->read_byte_buffer.begin(), this->read_byte_buffer.begin() + bytes_transferred,
std::back_inserter(temporary_recieve_data), [](std::byte character) {
return static_cast<char>(character);
}
);
this->read_async(); //Again Start the read operation
this->received_data += temporary_recieve_data;
this->receive_timeout_timer.cancel(); // Cancel existing timers if any are running
this->receive_timeout_timer.expires_after(boost::asio::chrono::milliseconds(SerialPort::bulk_data_receive_complete)); // Reset timer to current timestamp + 25 milliseconds
this->receive_timeout_timer.async_wait(boost::bind(&SerialPort::receive_timeout_handler, this, boost::asio::placeholders::error));
}
catch (const std::exception& ex)
{
}
}
void SerialPort::receive_timeout_handler(const boost::system::error_code& error)
{
try
{
if (error not_eq boost::system::errc::success) //Error in serial port read
{
return;
}
// this->signal(this->port_number, SerialPortEvents::read_data, this->received_data); //Signal to main thread that data has been received
}
catch (const std::exception& ex)
{
}
}
read_timer.cancel(); // Cancel existing timers if any are running
read_timer.expires_after(
SerialPort::bulk_data_receive_complete); // Reset timer to current timestamp + 25 milliseconds
Here the cancel is redundant, because setting the expiration cancels any pending wait.
You reschedule the timer regardless of whether it ran out. Your code misses the possibility that both the read and timer could have completed successfully. In that case your main gets signaled multiple times, even though it only "nearly" exceeded 25ms idle.
You would expect to see partially duplicated data, then, because received_data isn't cleared.
To clearly see what is going on, build your code with -DBOOST_ASIO_ENABLE_HANDLER_TRACKING=1 and run the output through handler_viz.pl (see also Cancelling boost asio deadline timer safely).
Suggestions
You could probably avoid the double firing by being explicit about the flow:
To achieve that, only cancel the read from the timeout handler:
void SerialPort::receive_timeout_handler(error_code ec) {
if (!ec.failed()) {
port.cancel(ec);
std::cerr << "read canceled: " << ec.message() << std::endl;
}
}
Then you could move the signal to the read-handler, where you expect the cancellation:
void SerialPort::read_handler(error_code ec, size_t bytes_transferred) {
if (ec == asio::error::operation_aborted) {
signal(port_number, SerialPortEvents::read_data, std::move(received_data));
} else if (ec.failed()) {
std::cerr << "SerialPort read: " << ec.message() << std::endl;
} else {
copy_n(begin(read_buffer), bytes_transferred, back_inserter(received_data));
read_timer.expires_after(bulk_data_receive_complete); // reset timer
read_timer.async_wait(boost::bind(&SerialPort::receive_timeout_handler, this, ph::error));
start_async_read(); // continue reading
}
}
To be completely fool-proof, you can check that the timer wasn't actually expired even on successful read (see again Cancelling boost asio deadline timer safely).
Intuitively, I think it makes more even sense to schedule the timer from start_async_read.
ASIDE #1
Currently your code completely ignores read_timeout (even aside from the unnecessary confusion between the argument read_timeout and the member read_timeout). It is unclear to me whether you want the read_timeout override argument to "stick" for the entire chain of read operations.
If you want it to stick, change the
start_async_read(bulk_data_receive_complete); // continue reading
call to
start_async_read(); // continue reading
below. I kept it like it is because it allows for easier timing demonstrations
ASIDE #2
I've undone the exception swallowing code. Instead of just squashing all exceptions into a boolean (which you'll then check to change control flow), use the native language feature to change the control flow, retaining error information.
Full Demo
Live On Coliru
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/signals2.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
namespace ph = boost::asio::placeholders;
using boost::system::error_code;
using namespace std::chrono_literals;
enum class SerialPortEvents { read_data };
class SerialPort {
using duration = std::chrono::system_clock::duration;
static constexpr duration //
ignore_timeout = duration::min(), // e.g. -0x8000000000000000ns
bulk_data_receive_complete = 25ms;
public:
SerialPort() : io(), port(io), read_timer(io) {}
void open_port(std::string device);
void start_async_read(duration read_timeout = ignore_timeout);
void run() {
if (io.stopped())
io.restart();
io.run();
}
boost::signals2::signal<void(unsigned, SerialPortEvents, std::string)> signal;
private:
void read_handler(error_code ec, size_t bytes_transferred);
void receive_timeout_handler(error_code ec);
duration read_timeout = bulk_data_receive_complete;
asio::io_context io;
asio::serial_port port;
asio::system_timer read_timer;
std::array<char, 8096> read_buffer;
std::string received_data;
// TODO
unsigned const port_number = 0;
};
void SerialPort::open_port(std::string device) { port.open(device); }
void SerialPort::start_async_read(duration timeout_override) {
read_buffer.fill(0); // Clear Buffer (TODO redundant)
if (timeout_override != ignore_timeout)
read_timeout = timeout_override;
std::cerr << "Expiry: " << read_timeout/1.s << "s from now" << std::endl;
read_timer.expires_after(read_timeout); // reset timer
read_timer.async_wait(boost::bind(&SerialPort::receive_timeout_handler, this, ph::error));
port.async_read_some( //
boost::asio::buffer(read_buffer),
boost::bind(&SerialPort::read_handler, this, ph::error, ph::bytes_transferred));
}
void SerialPort::read_handler(error_code ec, size_t bytes_transferred) {
if (ec == asio::error::operation_aborted) {
signal(port_number, SerialPortEvents::read_data, std::move(received_data));
} else if (ec.failed()) {
std::cerr << "SerialPort read: " << ec.message() << std::endl;
} else {
copy_n(begin(read_buffer), bytes_transferred, back_inserter(received_data));
start_async_read(bulk_data_receive_complete); // continue reading
}
}
void SerialPort::receive_timeout_handler(error_code ec) {
if (!ec.failed()) {
port.cancel(ec);
std::cerr << "read canceled: " << ec.message() << std::endl;
}
}
int main(int argc, char** argv) {
SerialPort sp;
sp.open_port(argc > 1 ? argv[1] : "COM3");
int count = 0;
sp.signal.connect([&count](unsigned port, SerialPortEvents event, std::string data) {
assert(port == 0);
assert(event == SerialPortEvents::read_data);
std::cout << "data #" << ++count << ": " << std::quoted(data) << "\n----" << std::endl;
});
sp.start_async_read(10s);
sp.run();
sp.start_async_read();
sp.run();
}
Testing with
socat -d -d pty,raw,echo=0 pty,raw,echo=0
./build/sotest /dev/pts/7
And various device emulations:
for a in hello world bye world; do sleep .01; echo "$a"; done >> /dev/pts/9
for a in hello world bye world; do sleep .025; echo "$a"; done >> /dev/pts/9
for a in hello world bye world; do sleep 1.0; echo "$a"; done >> /dev/pts/9
cat /etc/dictionaries-common/words >> /dev/pts/9
You can see all the outputs match with the expectations. With the sleep .025 you can see the input split over two read operations, but never with repeated data.
Handler tracking for the various runs: 1. 2.
3. 4.
The last one (literally throwing the dictionary at it) is way too big to be useful: https://imgur.com/a/I5lHnCV
Simplifying Notes
Note that your entire SerialPort re-implements a composed read operation. You might use simplify all that to asio::async_read_until with a MatchCondition.
This has the benefit of allowing directly asio::dynamic_buffer(received_data) as well.
Here's a simpler version that doesn't use a timer, but instead updates the deadline inside the manual run() loop.
It uses a single composed read operation with a MatchCondition that checks when the connection is "idle".
Live On Coliru
#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
using namespace std::chrono_literals;
enum class SerialPortEvents { read_data };
class SerialPort {
using Clock = std::chrono::system_clock;
using Duration = Clock::duration;
static constexpr Duration default_idle_timeout = 25ms;
public:
void open_port(std::string device);
void read_till_idle(Duration idle_timeout = default_idle_timeout);
std::function<void(unsigned, SerialPortEvents, std::string)> signal;
private:
asio::io_context io;
asio::serial_port port{io};
std::string received_data;
};
void SerialPort::open_port(std::string device) { port.open(device); }
namespace {
// Asio requires nested result_type to be MatchCondition... :(
template <typename F> struct AsMatchCondition {
using CBT = boost::asio::dynamic_string_buffer<char, std::char_traits<char>,
std::allocator<char>>::const_buffers_type;
using It = asio::buffers_iterator<CBT>;
using result_type = std::pair<It, bool>;
F _f;
AsMatchCondition(F f) : _f(std::move(f)) {}
auto operator()(It f, It l) const { return _f(f, l); }
};
}
void SerialPort::read_till_idle(Duration idle_timeout) {
if (io.stopped())
io.restart();
using T = Clock::time_point;
T start = Clock::now();
auto current_timeout = idle_timeout;
auto deadline = T::max();
auto is_idle = [&](T& new_now) { // atomic w.r.t. a new_now
new_now = Clock::now();
return new_now >= deadline;
};
auto update = [&](int invocation) {
auto previous = start;
bool idle = is_idle(start);
if (invocation > 0) {
current_timeout = default_idle_timeout; // or not, your choice
std::cerr << " [update deadline for current timeout:" << current_timeout / 1ms << "ms after "
<< (start - previous) / 1ms << "ms]" << std::endl;
}
deadline = start + current_timeout;
return idle;
};
int invocation = 0; // to avoid updating current_timeout on first invocation
auto condition = AsMatchCondition([&](auto, auto e) { return std::pair(e, update(invocation++)); });
async_read_until(port, asio::dynamic_buffer(received_data), condition,
[this](auto...) { signal(0, SerialPortEvents::read_data, std::move(received_data)); });
for (T t; !io.stopped(); io.run_for(5ms))
if (is_idle(t))
port.cancel();
}
void data_received(unsigned port, SerialPortEvents event, std::string data) {
static int count = 0;
assert(port == 0);
assert(event == SerialPortEvents::read_data);
std::cout << "data #" << ++count << ": " << std::quoted(data) << std::endl;
}
int main(int argc, char** argv) {
SerialPort sp;
sp.signal = data_received;
sp.open_port(argc > 1 ? argv[1] : "COM3");
sp.read_till_idle(3s);
}
Same local demos:
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)
Is it wise (or even safe) to use std::unique_lock as a class member? If so, are there any guidelines?
My thinking in using std::unique_lock was to ensure that the mutex is unlocked in the case of an exception being thrown.
The following code gives an example of how I'm currently using the unique_lock. I would like to know if I'm going in the wrong direction or not before the project grows too much.
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <unistd.h>
class WorkerClass {
private:
std::thread workerThread;
bool workerThreadRunning;
int workerThreadInterval;
int sharedResource;
std::mutex mutex;
std::unique_lock<std::mutex> workerMutex;
public:
WorkerClass() {
workerThreadRunning = false;
workerThreadInterval = 2;
sharedResource = 0;
workerMutex = std::unique_lock<std::mutex>(mutex);
unlockMutex();
}
~WorkerClass() {
stopWork();
}
void startWork() {
workerThreadRunning = true;
workerThread = std::thread(&WorkerClass::workerThreadMethod,
this);
}
void stopWork() {
lockMutex();
if (workerThreadRunning) {
workerThreadRunning = false;
unlockMutex();
workerThread.join();
}else {
unlockMutex();
}
}
void lockMutex() {
try {
workerMutex.lock();
}catch (std::system_error &error) {
std::cout << "Already locked" << std::endl;
}
}
void unlockMutex() {
try {
workerMutex.unlock();
}catch (std::system_error &error) {
std::cout << "Already unlocked" << std::endl;
}
}
int getSharedResource() {
int result;
lockMutex();
result = sharedResource;
unlockMutex();
return result;
}
void workerThreadMethod() {
bool isRunning = true;
while (isRunning) {
lockMutex();
sharedResource++;
std::cout << "WorkerThread: sharedResource = "
<< sharedResource << std::endl;
isRunning = workerThreadRunning;
unlockMutex();
sleep(workerThreadInterval);
}
}
};
int main(int argc, char *argv[]) {
int sharedResource;
WorkerClass *worker = new WorkerClass();
std::cout << "ThisThread: Starting work..." << std::endl;
worker->startWork();
for (int i = 0; i < 10; i++) {
sleep(1);
sharedResource = worker->getSharedResource();
std::cout << "ThisThread: sharedResource = "
<< sharedResource << std::endl;
}
worker->stopWork();
std::cout << "Done..." << std::endl;
return 0;
}
this is actually quite bad. storing a std::unique_lock or std::lock_guard as a member variable misses the point of scoped locking, and locking in general.
the idea is to have shared lock between threads, but each one temporary locks the shared resource the lock protects. the wrapper object makes it return-from-function safe and exception-safe.
you first should think about your shared resource. in the context of "Worker" I'd imagine some task queue. then, that task queue is associated with a some lock. each worker locks that lock with scoped-wrapper for queuing a task or dequeuing it. there is no real reason to keep the lock locked as long as some instance of a worker thread is alive, it should lock it when it needs to.
It is not a good idea to do that for a number of reasons. The first you're already "handling" with the try-catch block: two threads attempting to lock the same lock results in an exception. If you want non-blocking lock attempts you should use try_lock instead.
The second reason is that when std::unique_lock is stack-allocated in the scope of the duration of the lock, then when it is destructed it will unlock the resource for you. This means it is exception safe, if workerThread.join() throws in your current code then the lock will remain acquired.
I'm currently trying to log real-time data by using boost::thread and a check box. When I check the box, the logging thread starts. When I uncheck, the logging thread stops. The problem arises when I check/uncheck repeatedly and very fast (program crashes, some files aren't logged, etc.). How can I write a reliable thread-safe program where these problems don't occur when repeatedly and quickly checking/unchecking? I also don't want to use join() since this temporarily stops the data input coming from the main thread. In the secondary thread, I'm opening a log file, reading from a socket into a buffer, copying this into another buffer, and then writing this buffer to a log file. I'm thinking that maybe I should use mutex locks for reading/writing. If so, what specific locks should I use? Below is a code snippet:
//Main thread
if(m_loggingCheckBox->isChecked()) {
...
if(m_ThreadLogData.InitializeReadThread(socketInfo))//opens the socket.
//If socket is opened and can be read, start thread.
m_ThreadLogData.StartReadThread();
else
std::cout << "Did not initialize thread\n";
}
else if(!m_loggingCheckBox->isChecked())
{
m_ThreadLogData.StopReadThread();
}
void ThreadLogData::StartReadThread()
{
//std::cout << "Thread started." << std::endl;
m_stopLogThread = false;
m_threadSendData = boost::thread(&ThreadLogData::LogData,this);
}
void ThreadLogData::StopReadThread()
{
m_stopLogThread = true;
m_ReadDataSocket.close_socket(); // close the socket
if(ofstreamLogFile.is_open())
{
ofstreamLogFile.flush(); //flush the log file before closing it.
ofstreamLogFile.close(); // close the log file
}
m_threadSendData.interrupt(); // interrupt the thread
//m_threadSendData.join(); // join the thread. Commented out since this
temporarily stops data input.
}
//secondary thread
bool ThreadLogData::LogData()
{
unsigned short int buffer[1024];
bool bufferflag;
unsigned int iSizeOfBuffer = 1024;
int iSizeOfBufferRead = 0;
int lTimeout = 5;
if(!ofstreamLogFile.is_open())
{
ofstreamLogFile.open(directory_string().c_str(), ios::out);
if(!ofstreamLogFile.is_open())
{
return 0;
}
}
while(!m_stopLogThread)
{
try {
int ret = m_ReadDataSocket.read_sock(&m_msgBuffer.m_buffer
[0],iSizeOfBuffer,lTimeout,&iSizeOfBufferRead);
memcpy(&buffer[0],m_msgBuffer.m_buffer,iSizeOfBufferRead);
bufferflag = m_Buffer.setBuffer(buffer);
if(!bufferflag) return false;
object = &m_Buffer;
unsigned int data = object->getData();
ofstreamLogFile << data << std::endl;
boost::this_thread::interruption_point();
} catch (boost::thread_interrupted& interruption) {
std::cout << "ThreadLogData::LogData(): Caught Interruption thread." << std::endl;
StopReadThread();
} catch (...) {
std::cout << "ThreadLogData::LogData(): Caught Something." << std::endl;
StopReadThread();
}
} // end while()
}
I like to use Boost Asio for async stuff
#include <iostream>
#include <fstream>
#include <boost/asio.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/bind.hpp>
#include <boost/optional.hpp>
#include <thread>
using boost::asio::ip::tcp;
namespace asio = boost::asio;
struct program
{
asio::io_service _ioservice;
asio::deadline_timer _timer;
asio::signal_set _signals;
std::array<char, 1024> _buffer;
tcp::socket _client;
tcp::resolver _resolver;
std::ofstream _logfile;
std::thread _thread;
program()
: _timer(_ioservice),
_signals(_ioservice),
_client(_ioservice),
_resolver(_ioservice)
{
do_connect(_resolver.resolve({ "localhost", "6767" }));
do_toggle_logging_cycle();
_signals.add(SIGINT);
_signals.async_wait([this](boost::system::error_code ec, int) { if (!ec) close(); });
_thread = std::thread(boost::bind(&asio::io_service::run, boost::ref(_ioservice)));
}
~program()
{
if (_thread.joinable())
_thread.join();
}
void close() {
_ioservice.post([this]() {
_signals.cancel();
_timer.cancel();
_client.close();
});
}
private:
void do_toggle_logging_cycle(boost::system::error_code ec = {})
{
if (ec != boost::asio::error::operation_aborted)
{
if (_logfile.is_open())
{
_logfile.close();
_logfile.clear();
} else
{
_logfile.open("/tmp/output.log");
}
_timer.expires_from_now(boost::posix_time::seconds(2));
_timer.async_wait(boost::bind(&program::do_toggle_logging_cycle, this, boost::asio::placeholders::error()));
} else
{
std::cerr << "\nDone, goobye\n";
}
}
void do_connect(tcp::resolver::iterator endpoint_iterator) {
boost::asio::async_connect(
_client, endpoint_iterator,
[this](boost::system::error_code ec, tcp::resolver::iterator) {
if (!ec) do_read();
else close();
});
}
void do_read() {
boost::asio::async_read(
_client, asio::buffer(_buffer.data(), _buffer.size()),
[this](boost::system::error_code ec, std::size_t length) {
if (!ec) {
if (_logfile.is_open())
{
_logfile.write(_buffer.data(), length);
}
do_read();
} else {
close();
}
});
}
};
int main()
{
{
program p; // does socket reading and (optional) logging on a separate thread
std::cout << "\nMain thread going to sleep for 15 seconds...\n";
std::this_thread::sleep_for(std::chrono::seconds(15));
p.close(); // if the user doesn't press ^C, let's take the initiative
std::cout << "\nDestruction of program...\n";
}
std::cout << "\nMain thread ends\n";
};
The program connects to port 6767 of localhost and asynchronously reads data from it.
If logging is active (_logfile.is_open()), all received data is written to /tmp/output.log.
Now
the reading/writing is on a separate thread, but all operations are serialized using _ioservice (see e.g. the post in close())
the user can abort the the socket reading loop with Ctrl+C
every 2 seconds, the logging will be (de)activated (see do_toggle_logging_cycle)
The main thread just sleeps for 15 seconds before canceling the program (similar to the user pressing Ctrl-C).
The sample code looks long, but actually it's not so complicated :-)
What I'm trying to do is, when a user calls EventTimer.Start(), it will execute the callback handler (which is passed into the ctor) every interval milliseconds for repeatCount times.
You just need to look at the function EventTimer::Stop()
#include <iostream>
#include <string>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/function.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <ctime>
#include <sys/timeb.h>
#include <Windows.h>
std::string CurrentDateTimeTimestampMilliseconds() {
double ms = 0.0; // Milliseconds
struct timeb curtime;
ftime(&curtime);
ms = (double) (curtime.millitm);
char timestamp[128];
time_t now = time(NULL);
struct tm *tp = localtime(&now);
sprintf(timestamp, "%04d%02d%02d-%02d%02d%02d.%03.0f",
tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec, ms);
return std::string(timestamp);
}
class EventTimer
{
public:
static const int kDefaultInterval = 1000;
static const int kMinInterval = 1;
static const int kDefaultRepeatCount = 1;
static const int kInfiniteRepeatCount = -1;
static const int kDefaultOffset = 10;
public:
typedef boost::function<void()> Handler;
EventTimer(Handler handler = NULL)
: interval(kDefaultInterval),
repeatCount(kDefaultRepeatCount),
handler(handler),
timer(io),
exeCount(-1)
{
}
virtual ~EventTimer()
{
}
void SetInterval(int value)
{
// if (value < 1)
// throw std::exception();
interval = value;
}
void SetRepeatCount(int value)
{
// if (value < 1)
// throw std::exception();
repeatCount = value;
}
bool Running() const
{
return exeCount >= 0;
}
void Start()
{
io.reset(); // I don't know why I have to put io.reset here,
// since it's already been called in Stop()
exeCount = 0;
timer.expires_from_now(boost::posix_time::milliseconds(interval));
timer.async_wait(boost::bind(&EventTimer::EventHandler, this));
io.run();
}
void Stop()
{
if (Running())
{
// How to reset everything when stop is called???
//io.stop();
timer.cancel();
io.reset();
exeCount = -1; // Reset
}
}
private:
virtual void EventHandler()
{
// Execute the requested operation
//if (handler != NULL)
// handler();
std::cout << CurrentDateTimeTimestampMilliseconds() << ": exeCount = " << exeCount + 1 << std::endl;
// Check if one more time of handler execution is required
if (repeatCount == kInfiniteRepeatCount || ++exeCount < repeatCount)
{
timer.expires_at(timer.expires_at() + boost::posix_time::milliseconds(interval));
timer.async_wait(boost::bind(&EventTimer::EventHandler, this));
}
else
{
Stop();
std::cout << CurrentDateTimeTimestampMilliseconds() << ": Stopped" << std::endl;
}
}
private:
int interval; // Milliseconds
int repeatCount; // Number of times to trigger the EventHandler
int exeCount; // Number of executed times
boost::asio::io_service io;
boost::asio::deadline_timer timer;
Handler handler;
};
int main()
{
EventTimer etimer;
etimer.SetInterval(1000);
etimer.SetRepeatCount(1);
std::cout << CurrentDateTimeTimestampMilliseconds() << ": Started" << std::endl;
etimer.Start();
// boost::thread thrd1(boost::bind(&EventTimer::Start, &etimer));
Sleep(3000); // Keep the main thread active
etimer.SetInterval(2000);
etimer.SetRepeatCount(1);
std::cout << CurrentDateTimeTimestampMilliseconds() << ": Started again" << std::endl;
etimer.Start();
// boost::thread thrd2(boost::bind(&EventTimer::Start, &etimer));
Sleep(5000); // Keep the main thread active
}
/* Current Output:
20110520-125506.781: Started
20110520-125507.781: exeCount = 1
20110520-125507.781: Stopped
20110520-125510.781: Started again
*/
/* Expected Output (timestamp might be slightly different with some offset)
20110520-125506.781: Started
20110520-125507.781: exeCount = 1
20110520-125507.781: Stopped
20110520-125510.781: Started again
20110520-125512.781: exeCount = 1
20110520-125512.781: Stopped
*/
I don't know why that my second time of calling to EventTimer::Start() does not work at all. My questions are:
What should I do in
EventTimer::Stop() in order to reset
everything so that next time of
calling Start() will work?
Is there anything else I have to modify?
If I use another thread to start the EventTimer::Start() (see the commented code in the main function), when does the thread actually exit?
Thanks.
Peter
As Sam hinted, depending on what you're attempting to accomplish, most of the time it is considered a design error to stop an io_service. You do not need to stop()/reset() the io_service in order to reschedule a timer.
Normally you would leave a thread or thread pool running attatched to an io_service and then you would schedule whatever event you need with the io_service. With the io_service machinery in place, leave it up to the io_service to dispatch your scheduled work as requested and then you only have to work with the events or work requests that you schedule with the io_service.
It's not entirely clear to me what you are trying to accomplish, but there's a couple of things that are incorrect in the code you have posted.
io_service::reset() should only be invoked after a previous invocation of io_service::run() was stopped or ran out of work as the documentation describes.
you should not need explicit calls to Sleep(), the call to io_service::run() will block as long as it has work to do.
I figured it out, but I don't know why that I have to put io.reset() in Start(), since it's already been called in Stop().
See the updated code in the post.