简体   繁体   中英

Boost::asio::strand merges multiple handlers into one

I am currently using boost 1.70 and I was trying to implement io service loop to have a custom call between each invoked handle, and I couldn't get it to work. After some examination, I gained suspicion there are multiples handles executed in one call of "run_one" function. So I wrote a test code:

#include <boost/asio/io_service.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/post.hpp>

#include <thread>
#include <mutex>

class StrandPost
{
private:
    boost::asio::io_service           service_;
    boost::asio::io_service::work     work_;
    boost::asio::io_service::strand   strand_;

    std::thread                       module_thread_;
    void Run() {
        auto run_one = [this]() {
            std::cout << " ---- Running one ----" << std::endl;
            auto retval = service_.run_one();
            return retval;
        };

        while (run_one());
        std::cout << " ---- Ending run ----" << std::endl;
    }

public:
    StrandPost()
        : service_()
        , work_(service_)
        , strand_(service_)
        , module_thread_(&StrandPost::Run, this)
    {}

    ~StrandPost() {
        service_.stop();
        if (module_thread_.joinable()) {
            module_thread_.join();
        }
    }

    void PlanOutput(const std::string& string) {
        boost::asio::post(strand_,[string](){
            std::cout << string  <<std::endl;
        });
//        boost::asio::post(service_,[string](){
//            std::cout << string  <<std::endl;
//        });
    }
};

} // -----  end anonymous namespace   -----

int main() {
    StrandPost strand;

    strand.PlanOutput("First message");
    strand.PlanOutput("Second message");
    strand.PlanOutput("Third message");
    strand.PlanOutput("Fourth message");

    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 0;
}

And the output of that code confirmed my theory, because it was:

  • ---- Running one ----
  • First message
  • ---- Running one ----
  • Second message
  • Third message
  • Fourth message
  • ---- Running one ----
  • ---- Ending run ----

When using "io_service" directly, it works as expected, but when using "strand", after the first handle, multiple handles are executed as one.

So, the strand effectively merged several handlers into one. My question is:

  • Is this bug or is this intentional? Am I doing something wrong?
  • If this is a bug, is it reported? Because I could not find a mention of this anywhere.

I'm fairly certain this is intentional. The strand itself is a queue of jobs, that only one thread at a time can perform.

When io_service::run_one runs, it causes the thread to run the strand ready queue. I believe the 'only one once' logic isn't passed through to the strand's processing loop. Think of it it this way, the io_service is told to do one handler, but the strand's handler runs several jobs in sequence before returning.

The best fix for your issue is, if you're going to have your own io_service in your class, is don't use the strand at all, and post directly to the io_service. Then you'll have the behavior you desire.

This is, indeed, as intended. The strand_executor_service pops all ready handlers on the same strand:

void strand_executor_service::run_ready_handlers(implementation_type& impl)
{
  // Indicate that this strand is executing on the current thread.
  call_stack<strand_impl>::context ctx(impl.get());

  // Run all ready handlers. No lock is required since the ready queue is
  // accessed only within the strand.
  boost::system::error_code ec;
  while (scheduler_operation* o = impl->ready_queue_.front())
  {
    impl->ready_queue_.pop();
    o->complete(impl.get(), ec, 0);
  }
}

It is quite obvious that this can have a great performance improving impact.

Well, its not that easy, since I also need to be guaranteed that handles posted for execution from a given thread will be executed in the order of posting. Preserving order between posts from different threads is irrelevant, however order of posts from a given thread must be preserved, and as far as I know, "io_service" does not guarantee this. But thanks for the answer, looking further into the boost implementation, it looks you are completely right. – TStancek 6 hours ago

io_service does have the ordering guarantees of a strand (in fact, the strand's guarantees derive from that). In your case, there is - by definition - only one thread, so everything on the service will be in an implicit strand (see Why do I need strand per connection when using boost::asio? ).

Summary

You can do without the strand for the example code in your question.

If your situation is more involved and you need the one-by-one message processing control, you would do better to have a task queue that implements this explicitly, instead of depending on implementation details.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM