tdao
tdao

Reputation: 17713

Two threads running the same io_service

https://youtu.be/rwOv_tw2eA4?t=1030

This example has one io_service and two threads running on it.

void timer_expired( std:: string id )
{
    std::cout << timestamp() << ": " << id << ": begin\n";
    std::this_thread::sleep_for( std::chrono::seconds(3) );
    std::cout << timestamp() << ": " << id << ": end\n";
}

int main()
{
    boost::asio::io_service io_service;

    boost::asio::deadline_timer timer1( io_service, boost::posix_time::seconds(5) );
    boost::asio::deadline_timer timer2( io_service, boost::posix_time::seconds(5) );

    timer1.async_wait( []( auto ... ){ timer_expired("timer1"); });
    timer2.async_wait( []( auto ... ){ timer_expired("timer2"); });


    std::cout << timestamp() << ": calling io_service run\n";

    std::thread thread1( [&](){ io_service.run(); } );
    std::thread thread2( [&](){ io_service.run(); } );

    thread1.join();
    thread2.join();

    std::cout << timestamp() << ": done\n";

    return 0;
}

Every time I run this sample the output looks ok, in that:

The author stated that this code has race in there andshould not work (garage output).

What's not very clear is that we have two threads, each can serve one completion handler (here the timer callback). So why race? And the fact that I ran this code several times and unable to produce any garbage output as what the author presented.

The output looks as expected, here's a sample:

2019-07-28 11:27:44: calling io_service run
2019-07-28 11:27:49: timer1: begin
2019-07-28 11:27:49: timer2: begin
2019-07-28 11:27:52: timer1: end
2019-07-28 11:27:52: timer2: end
2019-07-28 11:27:52: done

Upvotes: 1

Views: 83

Answers (1)

rafix07
rafix07

Reputation: 20969

Handlers are invoked within io_service::run. You started two threads where io.run() works. So you have two runnig methods timer_expired at the same time. And the race is while accessing cout stream.

You are just lucky to see this nice output, but when you add some more works in timer_expired:

void timer_expired( std:: string id )
{
    std::cout << timestamp() << ": " << id << ": begin\n";
    std::this_thread::sleep_for( std::chrono::seconds(3) );
    // ADD MORE LINES TO BE PRINTED
    for (int i = 0; i < 1000; ++i)
        std::cout << timestamp() << ": " << id << ": end" << std::endl;
}

you will see interleaved characters.

Accessing by many threads cout object doesn't lead to crash (according to reference),

Concurrent access to a synchronized ([ios.members.static]) standard iostream object's formatted and unformatted input and output functions or a standard C stream by multiple threads shall not result in a data race. [ Note: Users must still synchronize concurrent use of these objects and streams by multiple threads if they wish to avoid interleaved characters.

but to avoid these interleaved chars you have to add synchronization when accessing cout for example by using std::mutex, or call handlers in serially manner - use strand object from boost.

Upvotes: 1

Related Questions