问题
In the 5th tutorial, of which the code I have given at bottom of the question, asio documentation introduced the output comes as follows :
Timer 2: 0
Timer 1: 1
Timer 2: 2
Timer 1: 3
Timer 2: 4
.
.
.
After the first one it is as expectable, with the sequence. But even though Timer1 is wrapped in the strand first, why does Timer 2 starts running 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;
}
回答1:
A strand is used to provide serial execution of handlers. Also, under certain conditions, it provides a guarantee on the order of invocation of handlers posted or dispatched through the strand. The example does not meet these conditions. Furthermore, there is no guarantee that one will observe the alternating pattern between the completion handlers.
IO Objects, such as timers, are not wrapped by strands, completion handlers are. A strand
can be thought of as being associated with a FIFO queue of handlers. If a handler queue has no handlers currently posted into an io_service
, then it will pop one handler from itself and post it into the associated io_service
. This flow guarantees that handlers posted into the same strand will not be invoked concurrently.
- strand.post() enqueues a handler into the strand.
- strand.dispatch() will run the handler if the current caller is running within the context of the strand. Otherwise, it will enqueue the handler as if by
post()
. - strand.wrap() return a new completion handler that, when invoked, will
dispatch()
the wrapped handler into the strand. Essentially,wrap()
defers the dispatching of a handler into the strand.
Given completion handlers a
and b
, if a
is enqueued before b
, then a
will be invoked before b
. This is the fundamental guarantee to which all scenarios can be reduced. The scenarios in which a
is guaranteed before b
are documented as followed:
strand.post(a)
happens beforestrand.post(b)
. Aspost()
does not attempt to invoke the provided handler withinpost()
,a
is enqueued beforeb
.strand.post(a)
happens beforestrand.dispatch(b)
, wherestrand.dispatch(b)
is performed outside of a strand. Asstrand.dispatch(b)
occurs outside of a strand,b
is queued as if bypost()
. Thus, this reduces down tostrand.post(a)
happening beforestrand.post(b)
.strand.dispatch(a)
happens beforestrand.post(b)
, wherestrand.dispatch(a)
occurs outside of a strand. Asstrand.dispatch(a)
occurs outside of a strand,a
is queued as if bypost()
. Thus, this reduces down tostrand.post(a)
happening beforestrand.post(b)
.strand.dispatch(a)
happens beforestrand.dispatch(b)
, where both are performed outside of the strand. As neither occur within a strand, both handlers are enqueued as if bypost()
. Thus, this reduces down tostrand.post(a)
happening beforestrand.post(b)
.
The io_service
makes no guarantees about the invocation order of handlers. Additionally, the handler returned from strand.wrap()
does not run within the context of a strand. The example code simplifies to:
auto wrapped_print1 = strand.wrap(&print1);
auto wrapped_print2 = strand.wrap(&print2);
timer1_.async_wait(wrapped_print1);
timer2_.async_wait(wrapped_print2);
If the async_wait
operations complete at the same time, the wrapped_print1
and wrapped_print2
completion handlers will be posted into the io_service
for deferred invocation. As the io_service
makes no guarantees on the invocation order, it may choose to invoke wrapped_print1
first, or it may choose to invoke wrapped_print2
first. Both wrapped_print
handlers are being invoked outside of the context of the strand in an unspecified order, resulting in print1()
and print2()
being enqueued into the strand in an unspecified order.
The unspecified order in which wrapped_print
are invoked is why one is not guaranteed to observe an alternating pattern between the print1
and print2
handlers in the original example. However, given the current implementation of the io_service
's internal scheduler, one will observe such a pattern.
来源:https://stackoverflow.com/questions/28010880/weird-output-for-a-tutorial-in-boost-asio-doc