Boost
        C++ Libraries
      
    
    ...one of the most highly
    regarded and expertly designed C++ library projects in the
    world.
 — Herb Sutter and Andrei
    Alexandrescu, C++
    Coding Standards
This version of Boost is under active development. You are currently in the develop branch. The current version is 1.89.0.
This tutorial demonstrates the use of the strand class template to synchronise completion handlers in a multithreaded program.
The previous four tutorials avoided the issue of handler synchronisation by calling the boost::asio::io_context::run() function from one thread only. As you already know, the asio library provides a guarantee that completion handlers will only be called from threads that are currently calling boost::asio::io_context::run(). Consequently, calling boost::asio::io_context::run() from only one thread ensures that completion handlers cannot run concurrently.
The single threaded approach is usually the best place to start when developing applications using asio. The downside is the limitations it places on programs, particularly servers, including:
If you find yourself running into these limitations, an alternative approach is to have a pool of threads calling boost::asio::io_context::run(). However, as this allows handlers to execute concurrently, we need a method of synchronisation when handlers might be accessing a shared, thread-unsafe resource.
#include <functional> #include <iostream> #include <thread> #include <boost/asio.hpp>
        We start by defining a class called printer,
        similar to the class in the previous tutorial. This class will extend the
        previous tutorial by running two timers in parallel.
      
class printer { public:
        In addition to initialising a pair of boost::asio::steady_timer members,
        the constructor initialises the strand_
        member, an object of type boost::asio::strand<boost::asio::io_context::executor_type>.
      
The strand class template is an executor adapter that guarantees that, for those handlers that are dispatched through it, an executing handler will be allowed to complete before the next one is started. This is guaranteed irrespective of the number of threads that are calling boost::asio::io_context::run(). Of course, the handlers may still execute concurrently with other handlers that were not dispatched through an strand, or were dispatched through a different strand object.
printer(boost::asio::io_context& io) : strand_(boost::asio::make_strand(io)), timer1_(io, boost::asio::chrono::seconds(1)), timer2_(io, boost::asio::chrono::seconds(1)), count_(0) {
When initiating the asynchronous operations, each completion handler is "bound" to an boost::asio::strand<boost::asio::io_context::executor_type> object. The boost::asio::bind_executor() function returns a new handler that automatically dispatches its contained handler through the strand object. By binding the handlers to the same strand, we are ensuring that they cannot execute concurrently.
timer1_.async_wait(boost::asio::bind_executor(strand_, std::bind(&printer::print1, this))); timer2_.async_wait(boost::asio::bind_executor(strand_, std::bind(&printer::print2, this))); } ~printer() { std::cout << "Final count is " << count_ << std::endl; }
        In a multithreaded program, the handlers for asynchronous operations should
        be synchronised if they access shared resources. In this tutorial, the shared
        resources used by the handlers (print1
        and print2) are std::cout
        and the count_ data member.
      
void print1() { if (count_ < 10) { std::cout << "Timer 1: " << count_ << std::endl; ++count_; timer1_.expires_at(timer1_.expiry() + boost::asio::chrono::seconds(1)); timer1_.async_wait(boost::asio::bind_executor(strand_, std::bind(&printer::print1, this))); } } void print2() { if (count_ < 10) { std::cout << "Timer 2: " << count_ << std::endl; ++count_; timer2_.expires_at(timer2_.expiry() + boost::asio::chrono::seconds(1)); timer2_.async_wait(boost::asio::bind_executor(strand_, std::bind(&printer::print2, this))); } } private: boost::asio::strand<boost::asio::io_context::executor_type> strand_; boost::asio::steady_timer timer1_; boost::asio::steady_timer timer2_; int count_; };
        The main function now causes
        boost::asio::io_context::run() to be called from two threads: the main thread
        and one additional thread. This is accomplished using an boost::thread object.
      
Just as it would with a call from a single thread, concurrent calls to boost::asio::io_context::run() will continue to execute while there is "work" left to do. The background thread will not exit until all asynchronous operations have completed.
int main() { boost::asio::io_context io; printer p(io); std::thread t([&]{ io.run(); }); io.run(); t.join(); return 0; }
See the full source listing
Return to the tutorial index
Previous: Timer.4 - Using a member function as a completion handler