67

How do I create a thread pool using boost in C++, and how do I assign tasks to the threadpool?

Jeroen
  • 15,257
  • 12
  • 59
  • 102
  • Only thing is it doesn't allow me to answer the other question, and self answering is allowed and encouraged. – Jeroen Oct 22 '13 at 16:55
  • You should be able to post an answer to [the other question](http://stackoverflow.com/questions/4084777/creating-a-thread-pool-using-boost), it isn't closed or [protected](http://meta.stackexchange.com/questions/52764/what-is-a-protected-question). – Sam Miller Oct 23 '13 at 23:17
  • I had it posted but it was deleted by staff. @SamMiller – Jeroen Oct 24 '13 at 05:24
  • 3
    Why was it deleted? Seems valid to me. If you re-post it to the original question I'll support you. – Neil Traft Dec 10 '13 at 23:58

3 Answers3

77

The process is pretty simple. First create an asio::io_service and a thread_group. Fill the thread_group with threads linked to the io_service. Assign tasks to the threads using the boost::bind function.

To stop the threads (usually when you are exiting your program) just stop the io_service and join all threads.

You should only need these headers:

#include <boost/asio/io_service.hpp>
#include <boost/bind.hpp>
#include <boost/thread/thread.hpp>

here is an example:

/*
 * Create an asio::io_service and a thread_group (through pool in essence)
 */
boost::asio::io_service ioService;
boost::thread_group threadpool;


/*
 * This will start the ioService processing loop. All tasks 
 * assigned with ioService.post() will start executing. 
 */
boost::asio::io_service::work work(ioService);

/*
 * This will add 2 threads to the thread pool. (You could just put it in a for loop)
 */
threadpool.create_thread(
    boost::bind(&boost::asio::io_service::run, &ioService)
);
threadpool.create_thread(
    boost::bind(&boost::asio::io_service::run, &ioService)
);

/*
 * This will assign tasks to the thread pool. 
 * More about boost::bind: "http://www.boost.org/doc/libs/1_54_0/libs/bind/bind.html#with_functions"
 */
ioService.post(boost::bind(myTask, "Hello World!"));
ioService.post(boost::bind(clearCache, "./cache"));
ioService.post(boost::bind(getSocialUpdates, "twitter,gmail,facebook,tumblr,reddit"));

/*
 * This will stop the ioService processing loop. Any tasks
 * you add behind this point will not execute.
*/
ioService.stop();

/*
 * Will wait till all the threads in the thread pool are finished with 
 * their assigned tasks and 'join' them. Just assume the threads inside
 * the threadpool will be destroyed by this method.
 */
threadpool.join_all();

Source: Recipes < Asio

SCFrench
  • 8,244
  • 2
  • 31
  • 61
Jeroen
  • 15,257
  • 12
  • 59
  • 102
  • 13
    The [`boost::asio::io_service::work`](http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference/io_service__work.html) object is a critical piece to get this to function properly. Also [`io_service::stop()`](http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference/io_service/stop.html) will prevent any additional task from executing, regardless of when the task is posted into the `io_service`. For example, while `getSocialUpdates()` is added to the `io_service` queue before `stop()`, if it is not mid-execution when `stop()` is invoked, then it will remain queued. – Tanner Sansbury Oct 23 '13 at 12:57
  • 7
    @TannerSansbury Actually this recipe makes me very confusing, since after io_service.stop() all my unfinished jobs get killed. A proper way should be removing the ioservice.stop() but destruct the work object, then call threadpool.join_all() to let all the jobs finish. – Terry Shi Apr 24 '14 at 02:56
  • 2
    See ["Stopping the io_service from running out of work" in the io_service documentation](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) on the difference between `io_service::stop()` (queued work is discarded) vs. the destroying the `work` object (queued work is drained). – vladr Apr 12 '17 at 14:55
  • 1
    If I use this recipe, not all the tasks are necessarily processed. In the sense that some of the functions (posted tasks) are not called. However, if I move the posting of the tasks above creation of the threadpool object, get rid of the work and change the order of the join and stop operations, everything works flawlessly. Is this normal? Am I missing something. I am using boost 1.54. – Halil Sen May 23 '17 at 16:03
  • 1
    This answer shouldn't be accepted as it simply does not work. The `ioService.stop()` breaks it. Contrary to the comment, that actually stops processing of any jobs that didn't happen to already complete while we were posting jobs. @HalilSen's fix is helpful however! – durka42 Feb 02 '23 at 03:09
53

Starting from boost 1.66.0, there is a thread_pool class:

#include <boost/asio/thread_pool.hpp>
#include <boost/asio/post.hpp>

boost::asio::thread_pool pool(4); // 4 threads
boost::asio::post(pool, [] {});
pool.join();

See the description.

pascx64
  • 904
  • 16
  • 31
Andrei
  • 750
  • 8
  • 9
14

I know you like code.

My Version

namespace bamthread
{
    typedef std::unique_ptr<boost::asio::io_service::work> asio_worker;

    struct ThreadPool {
        ThreadPool(size_t threads) :service(), working(new asio_worker::element_type(service)) {
            while(threads--)
            {
                auto worker = boost::bind(&boost::asio::io_service::run, &(this->service));
                g.add_thread(new boost::thread(worker));
            }
        }

        template<class F>
            void enqueue(F f){
                service.post(f);
            }

        ~ThreadPool() {
            working.reset(); //allow run() to exit
            g.join_all();
            service.stop();
        }

        private:
        boost::asio::io_service service; //< the io_service we are wrapping
        asio_worker working;
        boost::thread_group g; //< need to keep track of threads so we can join them
    };
}

Piece of Code to Use It:

{
    bamthread::ThreadPool tp(n_threads);
    BOOST_FOREACH(int y, boost::irange(starty, endy, step)){
        int im_x = 0;
        BOOST_FOREACH(int x, boost::irange(startx, endx, step)){
            tp.enqueue (boost::bind(&camera_view_depth::threaded_intersection, this,
                        intersections, 
                        intersected,
                        im_x,
                        im_y,
                        _faces, x, y));
            ++im_x;
        }
        ++im_y;
    }
}
squid
  • 2,597
  • 1
  • 23
  • 19