Unusual output for tutorial in boost asio doc
In the fifth tutorial, in which the code I gave at the bottom of the question, there was an output in the asio documentation:
Timer 2: 0
Timer 1: 1
Timer 2: 2
Timer 1: 3
Timer 2: 4
.
.
.
After the first, it looks like expected, with consistency. But even though Timer1 is chained first, why does Timer 2 start up first?
#include <iostream>
#include <asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
class printer
{
public:
printer(asio::io_service& io)
: strand_(io),
timer1_(io, boost::posix_time::seconds(1)),
timer2_(io, boost::posix_time::seconds(1)),
count_(0)
{
timer1_.async_wait(strand_.wrap(boost::bind(&printer::print1, this)));
timer2_.async_wait(strand_.wrap(boost::bind(&printer::print2, this)));
}
~printer()
{
std::cout << "Final count is " << count_ << "\n";
}
void print1()
{
if (count_ < 10)
{
std::cout << "Timer 1: " << count_ << "\n";
++count_;
timer1_.expires_at(timer1_.expires_at() + boost::posix_time::seconds(1));
timer1_.async_wait(strand_.wrap(boost::bind(&printer::print1, this)));
}
}
void print2()
{
if (count_ < 10)
{
std::cout << "Timer 2: " << count_ << "\n";
++count_;
timer2_.expires_at(timer2_.expires_at() + boost::posix_time::seconds(1));
timer2_.async_wait(strand_.wrap(boost::bind(&printer::print2, this)));
}
}
private:
asio::strand strand_;
asio::deadline_timer timer1_;
asio::deadline_timer timer2_;
int count_;
};
int main()
{
asio::io_service io;
printer p(io);
asio::thread t(boost::bind(&asio::io_service::run, &io));
io.run();
t.join();
system("PAUSE");
return 0;
}
source to share
A is strand
used to ensure sequential execution of handlers. In addition, under certain conditions, it provides a guarantee on the handling order of handlers dispatched or dispatched across a strand. The example does not meet these conditions. In addition, there is no guarantee that an interleaving pattern will be observed between completion handlers.
IO objects like timers are not threaded, completion handlers. A strand
can be considered associated with a queue of FIFO handlers. If there are no handlers in the handler queue that are currently placed in io_service
, then it will pull one handler out of itself and place it in the associated one io_service
. This thread ensures that handlers posted on the same chain will not be called at the same time.
-
strand.post()
puts the handler on the chain. -
strand.dispatch()
will run a handler if the current caller is running in the context of a string. Otherwise, it will unload the handler as if it werepost()
. -
strand.wrap()
return a new completion handler, which when called will be thedispatch()
handled handler in the chain. Essentiallywrap()
cancels sending the handler to the chain.
Completion handlers provided a
and b
, if a
queued before b
, then a
before will be called b
. This is the basic guarantee that all scenarios can be reduced. The scenarios that are a
guaranteed to be b
documented as follows:
-
strand.post(a)
happens beforestrand.post(b)
. Since itpost()
does not attempt to invoke the provided handler internallypost()
, ita
is queued beforeb
. -
strand.post(a)
occurs beforestrand.dispatch(b)
where itstrand.dispatch(b)
runs outside of the line. Because itstrand.dispatch(b)
happens outside of the line, itb
is enqueued as if onpost()
. So it is shortened tostrand.post(a)
priorstrand.post(b)
. -
strand.dispatch(a)
happens beforestrand.post(b)
, wherestrand.dispatch(a)
happens outside of the line. Because itstrand.dispatch(a)
happens outside of the line, ita
is queued as if onpost()
. So it is shortened tostrand.post(a)
priorstrand.post(b)
. -
strand.dispatch(a)
happens beforestrand.dispatch(b)
where both are executed outside of the line. Since neither of these occurs inside the thread, both handlers are queued as if they werepost()
. So it is shortened tostrand.post(a)
priorstrand.post(b)
.
io_service
makes no guarantees as to the order in which the processors are contacted. Also, the handler returned from strand.wrap()
does not run in the context of a thread. Sample code simplifies:
auto wrapped_print1 = strand.wrap(&print1);
auto wrapped_print2 = strand.wrap(&print2);
timer1_.async_wait(wrapped_print1);
timer2_.async_wait(wrapped_print2);
If the operations async_wait
complete at the same time, the completion handlers wrapped_print1
and wrapped_print2
will be sent in io_service
for the pending call. Since it io_service
does not make any guarantees in the order of handling, it can call first wrapped_print1
or it can call first wrapped_print2
. Both handlers are wrapped_print
called outside of the string context in an unspecified order, resulting in print1()
and print2()
chained in an unspecified order.
The indeterminate order in which it is called wrapped_print
is that the original example is not guaranteed to be able to observe the alternating pattern between the handlers print1
and print2
. However, given the current implementation of the internal scheduler io_service
, you will see a pattern like this.
source to share