Boost.Threads - FAQ

###  | # Boost.Threads ## Frequently Asked Questions (FAQs)

1. Are lock objects thread safe?
2a. Why was Boost.Threads modeled after (specific library name)?
2b. Why wasn't Boost.Threads modeled after (specific library name)?
3. Why do mutexes have noncopyable semantics?
4. How can you prevent deadlock from occurring when a thread must lock multiple mutexes?
5. Don't noncopyable mutex semantics mean that a class with a mutex member will be noncopyable as well?
6. How can you lock a mutex member in a const member function (in order to implement the monitor pattern)?
7. Why supply condition variables rather than event variables?
8. Why isn't thread cancellation or termination provided?
9. Is it safe for threads to share automatic storage duration (stack) objects via pointers or references?
10. Why has class semaphore disappeared?

## 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 any way. Share mutexes, not locks. For more information see the rationale behind the design for lock objects.

## 2a. Why was Boost.Threads modeled after (specific library name)?

It wasn't. Boost.Threads 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.Threads design is very much in the spirit of C++, and thus doesn't model such C based APIs.

## 2b. Why wasn't Boost.Threads 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 [Schmidt 00].

## 3. 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).

## 4. 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.Threads concept may wrap this pattern up in a reusable class.

## 5. 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;
    };

## 6. How can you lock a mutex member in a const member function, in order to implement the Monitor Pattern?

The Monitor Pattern mutex [Schmidt 00] 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.

## 7. Why supply condition variables rather than event variables?

Condition variables result in user code much less prone to race conditions than event variables. See Rationale for analysis. Also see [Hoare 74] and [Schmidt 00].

## 8. Why isn't thread cancellation or termination provided?

There's a valid need for thread termination, so at some point Boost.Threads probably will include it, but only after we can find a truly safe (and portable) mechanism for this concept.

## 9. 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).

## 10. 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. Dijkstra (the semaphore's inventor), Hoare, and Brinch Hansen all depreciated semaphores and advocated more structured alternatives. In a 1969 letter to Brinch Hansen, Wirth said "semaphores ... are not suitable for higher level languages." [Andrews-83] summarizes typical errors as "omitting a P or a V, or accidentally coding a P on one semaphore and a V on on another", forgetting to include all references to shared objects in critical sections, and confusion caused by using the same primitive for "both condition synchronization and mutual exclusion".

Revised 19 July, 2003

© Copyright William E. Kempf 2001-2002. All Rights Reserved.

Permission to use, copy, modify, distribute and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. William E. Kempf makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty.