The Boost C++ Libraries

Creating and Managing Threads

The most important class in this library is boost::thread, which is defined in boost/thread.hpp. This class is used to create a new thread. Example 44.1 is a simple example that creates a thread.

Example 44.1. Using boost::thread
#include <boost/thread.hpp>
#include <boost/chrono.hpp>
#include <iostream>

void wait(int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds{seconds});
}

void thread()
{
  for (int i = 0; i < 5; ++i)
  {
    wait(1);
    std::cout << i << '\n';
  }
}

int main()
{
  boost::thread t{thread};
  t.join();
}

The name of the function that the new thread should execute is passed to the constructor of boost::thread. Once the variable t in Example 44.1 is created, the function thread() starts immediately executing in its own thread. At this point, thread() executes concurrently with the main() function.

To keep the program from terminating, join() is called on the newly created thread. join() blocks the current thread until the thread for which join() was called has terminated. This causes main() to wait until thread() returns.

A particular thread can be accessed using a variable – t in this example – to wait for its termination. However, the thread will continue to execute even if t goes out of scope and is destroyed. A thread is always bound to a variable of type boost::thread in the beginning, but once created, the thread no longer depends on that variable. There is even a member function called detach() that allows a variable of type boost::thread to be decoupled from its corresponding thread. It’s not possible to call member functions like join() after calling detach() because the detached variable no longer represents a valid thread.

Anything that can be done inside a function can also be done inside a thread. Ultimately, a thread is no different from a function, except that it is executed concurrently to another function. In Example 44.1, five numbers are written to the standard output stream in a loop. To slow down the output, every iteration of the loop calls the wait() function to stall for one second. wait() uses the function sleep_for(), which is also provided by Boost.Thread and resides in the namespace boost::this_thread.

sleep_for() expects as its sole parameter a period of time that indicates how long the current thread should be stalled. By passing an object of type boost::chrono::seconds, a period of time is set. boost::chrono::seconds comes from Boost.Chrono which is introduced in Chapter 37.

sleep_for() only accepts types from Boost.Chrono. Even though Boost.Chrono has been part of the standard library with C++11, types from std::chrono cannot be used with Boost.Thread. Doing so will lead to compiler errors.

If you don’t want to call join() at the end of main(), you can use the class boost::scoped_thread.

Example 44.2. Waiting for a thread with boost::scoped_thread
#include <boost/thread.hpp>
#include <boost/thread/scoped_thread.hpp>
#include <boost/chrono.hpp>
#include <iostream>

void wait(int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds{seconds});
}

void thread()
{
  for (int i = 0; i < 5; ++i)
  {
    wait(1);
    std::cout << i << '\n';
  }
}

int main()
{
  boost::scoped_thread<> t{boost::thread{thread}};
}

The constructor of boost::scoped_thread expects an object of type boost::thread. In the destructor of boost::scoped_thread an action has access to that object. By default, boost::scoped_thread uses an action that calls join() on the thread. Thus, Example 44.2 works like Example 44.1.

You can pass a user-defined action as a template parameter. The action must be a class with an operator operator() that accepts an object of type boost::thread. boost::scoped_thread guarantees that the operator will be called in the destructor.

You can find the class boost::scoped_thread only in Boost.Thread. There is no counterpart in the standard library. Make sure you include the header file boost/thread/scoped_thread.hpp for boost::scoped_thread.

Example 44.3 introduces interruption points, which make it possible to interrupt threads. Interruption points are only supported by Boost.Thread and not by the standard library.

Example 44.3. An interruption point with boost::this_thread::sleep_for()
#include <boost/thread.hpp>
#include <boost/chrono.hpp>
#include <iostream>

void wait(int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds{seconds});
}

void thread()
{
  try
  {
    for (int i = 0; i < 5; ++i)
    {
      wait(1);
      std::cout << i << '\n';
    }
  }
  catch (boost::thread_interrupted&) {}
}

int main()
{
  boost::thread t{thread};
  wait(3);
  t.interrupt();
  t.join();
}

Calling interrupt() on a thread object interrupts the corresponding thread. In this context, interrupt means that an exception of type boost::thread_interrupted is thrown in the thread. However, this only happens when the thread reaches an interruption point.

Simply calling interrupt() does not have an effect if the given thread does not contain an interruption point. Whenever a thread reaches an interruption point it will check whether interrupt() has been called. If it has been called, an exception of type boost::thread_interrupted will be thrown.

Boost.Thread defines a series of interruption points such as the sleep_for() function. Because sleep_for() is called five times in Example 44.3, the thread checks five times whether or not it has been interrupted. Between the calls to sleep_for(), the thread can not be interrupted.

Example 44.3 doesn’t display five numbers, because interrupt() is called after three seconds in main(). Thus, the corresponding thread is interrupted and throws a boost::thread_interrupted exception. The exception is correctly caught inside the thread even though the catch handler is empty. Because the thread() function returns after the handler, the thread terminates as well. This, in turn, will cause the program to terminate because main() was waiting for the thread to terminate.

Boost.Thread defines about fifteen interruption points, including sleep_for(). These interruption points make it easy to interrupt threads in a timely manner. However, interruption points may not always be the best choice because they must be reached before the thread can check for a boost::thread_interrupted exception.

Example 44.4. Disabling interruption points with disable_interruption
#include <boost/thread.hpp>
#include <boost/chrono.hpp>
#include <iostream>

void wait(int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds{seconds});
}

void thread()
{
  boost::this_thread::disable_interruption no_interruption;
  try
  {
    for (int i = 0; i < 5; ++i)
    {
      wait(1);
      std::cout << i << '\n';
    }
  }
  catch (boost::thread_interrupted&) {}
}

int main()
{
  boost::thread t{thread};
  wait(3);
  t.interrupt();
  t.join();
}

The class boost::this_thread::disable_interruption prevents a thread from being interrupted. If you instantiate boost::this_thread::disable_interruption, interruption points in a thread will be disabled as long as the object exists. Thus, Example 44.4 displays five numbers because the attempt to interrupt the thread is ignored.

Example 44.5. Setting thread attributes with boost::thread::attributes
#include <boost/thread.hpp>
#include <boost/chrono.hpp>
#include <iostream>

void wait(int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds{seconds});
}

void thread()
{

  try
  {
    for (int i = 0; i < 5; ++i)
    {
      wait(1);
      std::cout << i << '\n';
    }
  }
  catch (boost::thread_interrupted&) {}
}

int main()
{
  boost::thread::attributes attrs;
  attrs.set_stack_size(1024);
  boost::thread t{attrs, thread};
  t.join();
}

boost::thread::attributes is used to set thread attributes. In version 1.56.0, you can only set one platform-independent attribute, the stack size. In Example 44.5, the stack size is set to 1024 bytes by boost::thread::attributes::set_stack_size().

Example 44.6. Detecting the thread ID and number of available processors
#include <boost/thread.hpp>
#include <iostream>

int main()
{
  std::cout << boost::this_thread::get_id() << '\n';
  std::cout << boost::thread::hardware_concurrency() << '\n';
}

In the namespace boost::this_thread, free-standing functions are defined that apply to the current thread. One of these functions is sleep_for(), which we have seen before. Another one is get_id(), which returns a number to uniquely identify the current thread (see Example 44.6). get_id() is also provided as a member function by the class boost::thread.

The static member function boost::thread::hardware_concurrency() returns the number of threads that can physically be executed at the same time, based on the underlying number of CPUs or CPU cores. Calling this function on a dual-core processor returns a value of 2. This function provides a simple method to identify the theoretical maximum number of threads that should be used.

Boost.Thread also provides the class boost::thread_group to manage threads in groups. One function this class provides, the member function join_all(), waits for all threads in the group to terminate.

Exercises

  1. Use two threads to calculate the sum of all numbers which are added up in the for-loop:

    #include <boost/timer/timer.hpp>
    #include <iostream>
    #include <cstdint>
    
    int main()
    {
        boost::timer::cpu_timer timer;
    
        std::uint64_t total = 0;
        for (int i = 0; i < 1'000'000'000; ++i)
            total += i;
    
        std::cout << timer.format();
        std::cout << total << '\n';
    }
    
  2. Generalize the program so that it uses as many threads as can be executed concurrently on a computer. For example, the program should use four threads if it is run on a computer with a CPU with four cores.