| Home | Libraries | People | FAQ | More

## Frequently Asked Questions

1. Are lock objects thread safe?
2. Why was Boost.Thread modeled after (specific library name)?
3. Why wasn't Boost.Thread modeled after (specific library name)?
4. Why do Mutexes have noncopyable semantics?
5. How can you prevent deadlock from occurring when a thread must lock multiple mutexes?
6. Don't noncopyable Mutex semantics mean that a class with a mutex member will be noncopyable as well?
7. How can you lock a Mutex member in a const member function, in order to implement the Monitor Pattern?
8. Why supply boost::condition variables rather than event variables?
9. Why isn't thread cancellation or termination provided?
10. Is it safe for threads to share automatic storage duration (stack) objects via pointers or references?
11. Why has class semaphore disappeared?
12. Why doesn't the thread's ctor take at least a void* to pass any information along with the function? All other threading libs support that and it makes Boost.Threads inferiour.

1. | Are lock objects thread safe?
| No! Lock objects are not meant to be shared between threads. They are meant to be short-lived objects created on automatic storage within a code block. Any other usage is just likely to lead to errors and won't really be of actual benefit anyway. Share Mutexes, not Locks. For more information see the rationale behind the design for lock objects.
2. | Why was Boost.Thread modeled after (specific library name)?
| It wasn't. Boost.Thread was designed from scratch. Extensive design discussions involved numerous people representing a wide range of experience across many platforms. To ensure portability, the initial implements were done in parallel using POSIX Threads and the Win32 threading API. But the Boost.Thread design is very much in the spirit of C++, and thus doesn't model such C based APIs.
3. | Why wasn't Boost.Thread modeled after (specific library name)?

Existing C++ libraries either seemed dangerous (often failing to take advantage of prior art to reduce errors) or had excessive dependencies on library components unrelated to threading. Existing C libraries couldn't meet our C++ requirements, and were also missing certain features. For instance, the WIN32 thread API lacks condition variables, even though these are critical for the important Monitor pattern [SchmidtStalRohnertBuschmann].

4. | Why do Mutexes have noncopyable semantics?

To ensure that deadlocks don't occur. The only logical form of copy would be to use some sort of shallow copy semantics in which multiple mutex objects could refer to the same mutex state. This means that if ObjA has a mutex object as part of its state and ObjB is copy constructed from it, then when ObjB::foo() locks the mutex it has effectively locked ObjA as well. This behavior can result in deadlock. Other copy semantics result in similar problems (if you think you can prove this to be wrong then supply us with an alternative and we'll reconsider).

5. | How can you prevent deadlock from occurring when a thread must lock multiple mutexes?
| Always lock them in the same order. One easy way of doing this is to use each mutex's address to determine the order in which they are locked. A future Boost.Thread concept may wrap this pattern up in a reusable class.
6. | Don't noncopyable Mutex semantics mean that a class with a mutex member will be noncopyable as well?

No, but what it does mean is that the compiler can't generate a copy constructor and assignment operator, so they will have to be coded explicitly. This is a good thing, however, since the compiler generated operations would not be thread-safe. The following is a simple example of a class with copyable semantics and internal synchronization through a mutex member.

    class counter
    {
    public:
       // Doesn't need synchronization since there can be no references to *this
       // until after it's constructed!
       explicit counter(int initial_value)
          : m_value(initial_value)
       {
       }
       // We only need to synchronize other for the same reason we don't have to
       // synchronize on construction!
       counter(const counter& other)
       {
          boost::mutex::scoped_lock scoped_lock(other.m_mutex);
          m_value = other.m_value;
       }
       // For assignment we need to synchronize both objects!
       const counter& operator=(const counter& other)
       {
          if (this == &other)
             return *this;
          boost::mutex::scoped_lock lock1(&m_mutex < &other.m_mutex ? m_mutex : other.m_mutex);
          boost::mutex::scoped_lock lock2(&m_mutex > &other.m_mutex ? m_mutex : other.m_mutex);
          m_value = other.m_value;
          return *this;
       }
       int value() const
       {
          boost::mutex::scoped_lock scoped_lock(m_mutex);
          return m_value;
       }
       int increment()
       {
          boost::mutex::scoped_lock scoped_lock(m_mutex);
          return ++m_value;
       }
    private:
       mutable boost::mutex m_mutex;
       int m_value;
    };

7. | How can you lock a Mutex member in a const member function, in order to implement the Monitor Pattern?

The Monitor Pattern [SchmidtStalRohnertBuschmann] mutex should simply be declared as mutable. See the example code above. The internal state of mutex types could have been made mutable, with all lock calls made via const functions, but this does a poor job of documenting the actual semantics (and in fact would be incorrect since the logical state of a locked mutex clearly differs from the logical state of an unlocked mutex). Declaring a mutex member as mutable clearly documents the intended semantics.

8. | Why supply `boost::condition` variables rather than event variables?
| Condition variables result in user code much less prone to race conditions than event variables. See the section called “Rationale for not providing Event Variables” for analysis. Also see [Hoare74] and [SchmidtStalRohnertBuschmann].
9. | Why isn't thread cancellation or termination provided?
| There's a valid need for thread termination, so at some point Boost.Thread probably will include it, but only after we can find a truly safe (and portable) mechanism for this concept.
10. | Is it safe for threads to share automatic storage duration (stack) objects via pointers or references?

Only if you can guarantee that the lifetime of the stack object will not end while other threads might still access the object. Thus the safest practice is to avoid sharing stack objects, particularly in designs where threads are created and destroyed dynamically. Restrict sharing of stack objects to simple designs with very clear and unchanging function and thread lifetimes. (Suggested by Darryl Green).

11. | Why has class semaphore disappeared?
| Semaphore was removed as too error prone. The same effect can be achieved with greater safety by the combination of a mutex and a condition variable.
12. | Why doesn't the thread's ctor take at least a void* to pass any information along with the function? All other threading libs support that and it makes Boost.Threads inferiour.

There is no need, because Boost.Threads are superiour! First thing is that its ctor doesn't take a function but a functor. That means that you can pass an object with an overloaded operator() and include additional data as members in that object. Beware though that this object is copied, use boost::ref to prevent that. Secondly, even a boost::function<void (void)> can carry parameters, you only have to use boost::bind() to create it from any function and bind its parameters.

That is also why Boost.Threads are superiour, because they don't require you to pass a type-unsafe void pointer. Rather, you can use the flexible Boost.Functions to create a thread entry out of anything that can be called.

Last revised: April 24, 2007 at 16:11:17 GMT | Copyright © 2001-2003 William E. Kempf