15

What's the proper usage of settings up a thread pool for io_service? These 2 statements from the documentation are throwing me off:

io_service::run

A normal exit from the run() function implies that the io_service object is stopped (the stopped() function returns true). Subsequent calls to run(), run_one(), poll() or poll_one() will return immediately unless there is a prior call to reset().

io_service::reset

This function must be called prior to any second or later set of invocations of the run(), run_one(), poll() or poll_one() functions when a previous invocation of these functions returned due to the io_service being stopped or running out of work.

Here's what I'm currently doing:

boost::thread_group     m_Threads;
boost::asio::io_service m_IoService;
boost::barrier          m_Barrier(numThreads);

for( unsigned int i = 0; i < numThreads; ++i )
{
    m_Threads.create_thread(
        [&]()
        {
            for(;;)
            {
                m_IoService.run();

                if( m_Barrier.wait() )  //  will only return true for 1 thread
                {
                    m_IoService.reset();
                }
                m_Barrier.wait();
            }
        });
}

m_IoService.stop();
m_Threads.interrupt_all();
m_Threads.join_all();

Everything seems to work fine if I just put m_IoService.run() in an infinite loop (which the documentation seems to indicate should not be the case). What's the correct way?

Community
  • 1
  • 1
David
  • 27,652
  • 18
  • 89
  • 138
  • It was my understanding that you just called run once and it only returned when there was no more work queued. I've never called it in a loop. I may be mistunderstanding something though – jcoder Oct 31 '11 at 17:20
  • 1
    @JohnB Well I've got all these objects which use the io_service all over the place reading and writing and whatnot. It's entirely possible io_service runs out of work for a moment only to then get a bunch more work queued up again isn't it? I'm trying to handle that case and also distribute over multiple threads. – David Oct 31 '11 at 17:27
  • 1
    Well the idea is that you'll always queue up the next read or write request before finishing processing the one you are handling. If you can't do that you can can use the work object as described here http://www.boost.org/doc/libs/1_42_0/doc/html/boost_asio/reference/io_service.html#boost_asio.reference.io_service.stopping_the_io_service_from_running_out_of_work – jcoder Oct 31 '11 at 17:31
  • @JohnB To address your first sentence: In my code many systems/threads are executing reads/writes whenever they see fit. They have no knowledge of each other and there may or may not be enough *work* to keep io_service's queue non-empty throughout the lifetime of the application. I can't envision a nontrivial use of asio which could guarantee an unbroken stream of work for io_service to do. – David Oct 31 '11 at 17:48

1 Answers1

17

run() is a blocking call, and will execute all events that it can before returning. It will only return if there are no more events to handle. Once it returns, you must call reset() on the io_service before calling run() again.

You can have multiple threads calling run() - this is not a problem, and you don't need the infinite loop as long as the io_service has some work to do. The normal pattern for this is to create a work object on the io_service which will force run() to never return. This does mean that you explicitly have to call stop() on the io_service when you are done as it will never naturally exit.

If you setup a work on the io_service, it will never naturally exit and therefore you will never need to call reset().

work some_work(m_IoService); // this will keep the io_service live.

for( unsigned int i = 0; i < numThreads; ++i )
{
  m_Threads.create_thread(
    [&]()
    {
      m_IoService.run();
    });
}

Now all threads are dispatching events on the io_service

// Now post your jobs
m_IoService.post(boost::bind(...)); // this will be executed in one of the threads
m_IoService.post(boost::bind(...)); // this will be executed in one of the threads
m_IoService.post(boost::bind(...)); // this will be executed in one of the threads

m_IoService.stop(); // explicitly stop the io_service
// now join all the threads and wait for them to exit
Nim
  • 33,299
  • 2
  • 62
  • 101
  • Random threading question: Is there any need to call `boost::this_thread::yield()` after `m_IoService.run()`? – David Oct 31 '11 at 17:39
  • 3
    @Dave - it's pointless, execution will only reach that point once `run()` exits. – Nim Oct 31 '11 at 17:50
  • 4
    I would argue that it's cleaner to always let ``run()`` exit cleanly. If you need to make sure the ``io_service`` stays alive when you're out of events, create a ``work`` object and destroy it when it's time to exit. That way all pending events will fire, allowing everything to clean up. – Arvid Nov 01 '11 at 05:39
  • what if I change post to dispatch? Jobs will be executed by different threads? – jean Sep 21 '12 at 04:27
  • 1
    @jean, `post()` will *always* queue (and therefore be executed in a different context), `dispatch()` *may execute* in the *current context* if possible, else will queue (like `post()`). – Nim Sep 21 '12 at 07:19