Thread Local Storage
Many functions are not implemented to be reentrant. This means that it is unsafe to call the function while another thread is calling the same function. A non-reentrant function holds static data over successive calls or returns a pointer to static data. For example, std::strtok
is not reentrant because it uses static data to hold the string to be broken into tokens.
A non-reentrant function can be made into a reentrant function using two approaches. One approach is to change the interface so that the function takes a pointer or reference to a data type that can be used in place of the static data previously used. For example, POSIX defines strtok_r
, a reentrant variant of std::strtok
, which takes an extra char**
parameter thats used instead of static data. This solution is simple and gives the best possible performance; however, it means changing the public interface, which potentially means changing a lot of code. The other approach leaves the public interface as is and replaces the static data with thread local storage (sometimes referred to as thread-specific storage).
Thread local storage is data thats associated with a specific thread (the current thread). Multithreading libraries give access to thread local storage through an interface that allows access to the current threads instance of the data. Every thread gets its own instance of this data, so theres never an issue with concurrent access. However, access to thread local storage is slower than access to static or local data; therefore its not always the best solution. However, its the only solution available when its essential not to change the public interface.
Boost.Threads provides access to thread local storage through the smart pointer boost::thread_specific_ptr
. The first time every thread tries to access an instance of this smart pointer, it has a NULL
value, so code should be written to check for this and initialize the pointer on first use. The Boost.Threads library ensures that the data stored in thread local storage is cleaned up when the thread exits.
Listing Five illustrates a very simple use of the boost::thread_specific_ptr
class. Two new threads are created to initialize the thread local storage
and then loop 10 times incrementing the integer contained in the smart pointer
and writing the result to std::cout
(which is synchronized with a mutex
because it is a shared resource). The main
thread then waits for these
two threads to complete. The output of this example clearly shows that each
thread is operating on its own instance of data, even though both are using
the same boost::thread_specific_ptr
.
Listing Six: The boost::thread_specific_ptr class.
#include <boost/thread/thread.hpp> #include <boost/thread/mutex.hpp> #include <boost/thread/tss.hpp> #include <iostream> boost::mutex io_mutex; boost::thread_specific_ptr<int> ptr; struct count { count(int id) : id(id) { } void operator()() { if (ptr.get() == 0) ptr.reset(new int(0)); for (int i = 0; i < 10; ++i) { (*ptr)++; boost::mutex::scoped_lock lock(io_mutex); std::cout << id << ": " << *ptr << std::endl; } } int id; }; int main(int argc, char* argv[]) { boost::thread thrd1(count(1)); boost::thread thrd2(count(2)); thrd1.join(); thrd2.join(); return 0; }
Once Routines
Theres one issue left to deal with: how to make initialization routines (such as constructors) thread-safe. For example, when a global instance of an object is created as a singleton for an application, knowing that theres an issue with the order of instantiation, a function is used that returns a static instance, ensuring the static instance is created the first time the method is called. The problem here is that if multiple threads call this function at the same time, the constructor for the static instance may be called multiple times as well, with disastrous results.
The solution to this problem is whats known as a once routine. A once routine is called only once by an application. If multiple threads try to call the routine at the same time, only one actually is able to do so while all others wait until that thread has finished executing the routine. To ensure that it is executed only once, the routine is called indirectly by another function thats passed a pointer to the routine and a reference to a special flag type used to check if the routine has been called yet. This flag is initialized using static initialization, which ensures that it is initialized at compile time and not run time. Therefore, it is not subject to multithreaded initialization problems. Boost.Threads provides calling once routines through boost::call_once
and also defines the flag type boost::once_flag
and a special macro used to statically initialize the flag named BOOST_ONCE_INIT
.
Listing Six illustrates a very simple use of boost::call_once
.
A global integer is statically initialized to zero and an instance of boost::once_flag
is statically initialized using BOOST_ONCE_INIT
. Then main
starts
two threads, both trying to initialize the global integer by calling
boost::call_once
with a pointer to a function that increments the integer.
Next main
waits for these two threads to complete and writes out the
final value of the integer to std::cout
. The output illustrates that
the routine truly was only called once because the value of the integer is
only one.
Listing Six: A very simple use of boost::call_once.
#include <boost/thread/thread.hpp> #include <boost/thread/once.hpp> #include <iostream> int i = 0; boost::once_flag flag = BOOST_ONCE_INIT; void init() { ++i; } void thread() { boost::call_once(&init, flag); } int main(int argc, char* argv[]) { boost::thread thrd1(&thread); boost::thread thrd2(&thread); thrd1.join(); thrd2.join(); std::cout << i << std::endl; return 0; }
The Future of Boost.Threads
There are several additional features planned for Boost.Threads. There will be a boost::read_write_mutex
, which will allow multiple threads to read from the shared resource at the same time, but will ensure exclusive access to any threads writing to the shared resource. There will also be a boost::thread_barrier
, which will make a set of threads wait until all threads have entered the barrier. A boost::thread_pool
is also planned to allow for short routines to be executed asynchronously without the need to create or destroy a thread each time.
Boost.Threads has been presented to the C++ Standards Committees Library Working Group for possible inclusion in the Standards upcoming Library Technical Report, as a prelude to inclusion in the next version of the Standard. The committee may consider other threading libraries; however, they viewed the initial presentation of Boost.Threads favorably, and they are very interested in adding some support for multithreaded programming to the Standard. So, the future is looking good for multithreaded programming in C++.
References
[1] The POSIX standard defines multithreaded support in whats commonly known as the pthread library. This provides multithreaded support for a wide range of operating systems, including Win32 through the pthreads-win32 port. However, this is a C library that fails to address some C++ concepts and is not available on all platforms.
[2] Visit the Boost website at http://www.boost.org.
[3] See Bjorn Karlsson's article, Smart Pointers in Boost, C/C++ Users Journal, April 2002.
[4] Douglas Schmidt, Michael Stal, Hans Rohnert, and Frank Buschmann. Pattern-Oriented Software Architecture Volume 2 Patterns for Concurrent and Networked Objects (Wiley, 2000).
William E. Kempf received his BS in CompSci/Math from Doane College. Hes been in the industry for 10 years and is currently a senior application developer for First Data Resources, Inc. He is the author of the Boost.Threads library, and an active Boost member. He can be contacted at [email protected].