|
Boost-Commit : |
Subject: [Boost-commit] svn:boost r82036 - trunk/libs/thread/doc
From: vicente.botet_at_[hidden]
Date: 2012-12-16 17:40:11
Author: viboes
Date: 2012-12-16 17:40:10 EST (Sun, 16 Dec 2012)
New Revision: 82036
URL: http://svn.boost.org/trac/boost/changeset/82036
Log:
Thread: update doc with internal and external locking
Added:
trunk/libs/thread/doc/external_locking.qbk (contents, props changed)
trunk/libs/thread/doc/internal_locking.qbk (contents, props changed)
Text files modified:
trunk/libs/thread/doc/changes.qbk | 34 +-
trunk/libs/thread/doc/mutex_concepts.qbk | 454 +++++++++++++++++++++++++++++++++++++--
trunk/libs/thread/doc/shared_mutex_ref.qbk | 6
trunk/libs/thread/doc/sync_tutorial.qbk | 9
trunk/libs/thread/doc/thread.qbk | 4
5 files changed, 449 insertions(+), 58 deletions(-)
Modified: trunk/libs/thread/doc/changes.qbk
==============================================================================
--- trunk/libs/thread/doc/changes.qbk (original)
+++ trunk/libs/thread/doc/changes.qbk 2012-12-16 17:40:10 EST (Sun, 16 Dec 2012)
@@ -46,23 +46,22 @@
* [@http://svn.boost.org/trac/boost/ticket/7413 #7413] C++11 compliance: Add async when the launch policy is deferred.
* [@http://svn.boost.org/trac/boost/ticket/7414 #7414] C++11 compliance: future::get post-condition should be valid()==false.
* [@http://svn.boost.org/trac/boost/ticket/7414 #7444] Async: Add make_future/make_shared_future.
-* [@http://svn.boost.org/trac/boost/ticket/7445 #7445] Async: Add future<>.then.
* [@http://svn.boost.org/trac/boost/ticket/7449 #7449] Synchro: Add a synchronized value class.
* [@http://svn.boost.org/trac/boost/ticket/7540 #7540] Threads: Add a helper class that join a thread on destruction.
* [@http://svn.boost.org/trac/boost/ticket/7541 #7541] Threads: Add a thread wrapper class that joins on destruction.
* [@http://svn.boost.org/trac/boost/ticket/7575 #7575] C++11 compliance: A future created by async should "join" in the destructor.
-* [@http://svn.boost.org/trac/boost/ticket/7587 #7587] Synchro: Add strict_lock and nested_strict_lock
-* [@http://svn.boost.org/trac/boost/ticket/7588 #7588] Synchro: Split the locks.hpp in several files to limit dependencies
-* [@http://svn.boost.org/trac/boost/ticket/7589 #7589] Synchro: Add polymorphic lockables
-* [@http://svn.boost.org/trac/boost/ticket/7590 #7590] Synchro: Add lockable concept checkers based on Boost.ConceptCheck
-* [@http://svn.boost.org/trac/boost/ticket/7591 #7591] Add lockable traits that can be used with enable_if
-* [@http://svn.boost.org/trac/boost/ticket/7592 #7592] Synchro: Add a null_mutex that is a no-op and that is a model of UpgardeLockable
-* [@http://svn.boost.org/trac/boost/ticket/7593 #7593] Synchro: Add a externally_locked class
-* [@http://svn.boost.org/trac/boost/ticket/7590 #7594] Threads: Allow to disable thread interruptions
+* [@http://svn.boost.org/trac/boost/ticket/7587 #7587] Synchro: Add strict_lock and nested_strict_lock.
+* [@http://svn.boost.org/trac/boost/ticket/7588 #7588] Synchro: Split the locks.hpp in several files to limit dependencies.
+* [@http://svn.boost.org/trac/boost/ticket/7589 #7589] Synchro: Add polymorphic lockables.
+* [@http://svn.boost.org/trac/boost/ticket/7590 #7590] Synchro: Add lockable concept checkers based on Boost.ConceptCheck.
+* [@http://svn.boost.org/trac/boost/ticket/7591 #7591] Add lockable traits that can be used with enable_if.
+* [@http://svn.boost.org/trac/boost/ticket/7592 #7592] Synchro: Add a null_mutex that is a no-op and that is a model of UpgardeLockable.
+* [@http://svn.boost.org/trac/boost/ticket/7593 #7593] Synchro: Add a externally_locked class.
+* [@http://svn.boost.org/trac/boost/ticket/7590 #7594] Threads: Allow to disable thread interruptions.
Fixed Bugs:
-* [@http://svn.boost.org/trac/boost/ticket/7657 #7657] Serious performance and memory consuption hit if condition_variable methods condition notify_one or notify_all is used repeatedly.
+* [@http://svn.boost.org/trac/boost/ticket/7657 #7657] Serious performance and memory consumption hit if condition_variable methods condition notify_one or notify_all is used repeatedly.
* [@http://svn.boost.org/trac/boost/ticket/7668 #7668] thread_group::join_all() should check whether its threads are joinable.
* [@http://svn.boost.org/trac/boost/ticket/7669 #7669] thread_group::join_all() should catch resource_deadlock_would_occur.
* [@http://svn.boost.org/trac/boost/ticket/7672 #7672] lockable_traits.hpp syntax error: "defined" token misspelled.
@@ -335,23 +334,16 @@
The following features will be included in next releases.
# Complete the C++11 missing features, in particular
- * [@http://svn.boost.org/trac/boost/ticket/7413 #7413] c++11 compliance: Add async when the launch policy is deferred.
- * [@http://svn.boost.org/trac/boost/ticket/7280 #7280] C++11 compliance: Add promise::...at_thread_exit functions.
- * [@http://svn.boost.org/trac/boost/ticket/7282 #7282] C++11 compliance: Add packaged_task::make_ready_at_thread_exit function.
* [@http://svn.boost.org/trac/boost/ticket/7285 #7285] C++11 compliance: Allow to pass movable arguments for call_once.
* [@http://svn.boost.org/trac/boost/ticket/6227 #6227] C++11 compliance: Use of variadic templates on Generic Locking Algorithms on compilers providing them.
# Add some of the extension proposed in [@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3428.pdf A Standardized Representation of Asynchronous Operations], in particular
- * [@http://svn.boost.org/trac/boost/ticket/7445 #7445] Async: Add future<>.then
- * [@http://svn.boost.org/trac/boost/ticket/7446 #7446] Async: Add when_any
- * [@http://svn.boost.org/trac/boost/ticket/7447 #7447] Async: Add when_all
- * [@http://svn.boost.org/trac/boost/ticket/7448 #7448] Async: Add async taking a scheduler parameter
-
-
-# Add a synchronized value class following the design in [@http://www.drdobbs.com/cpp/enforcing-correct-mutex-usage-with-synch/225200269 Enforcing Correct Mutex Usage with Synchronized Values]
-
+ * [@http://svn.boost.org/trac/boost/ticket/7445 #7445] Async: Add future<>.then.
+ * [@http://svn.boost.org/trac/boost/ticket/7446 #7446] Async: Add when_any.
+ * [@http://svn.boost.org/trac/boost/ticket/7447 #7447] Async: Add when_all.
+ * [@http://svn.boost.org/trac/boost/ticket/7448 #7448] Async: Add async taking a scheduler parameter.
[endsect]
Added: trunk/libs/thread/doc/external_locking.qbk
==============================================================================
--- (empty file)
+++ trunk/libs/thread/doc/external_locking.qbk 2012-12-16 17:40:10 EST (Sun, 16 Dec 2012)
@@ -0,0 +1,583 @@
+[/
+ / Copyright (c) 2008,2012 Vicente J. Botet Escriba
+ /
+ / Distributed under the Boost Software License, Version 1.0. (See accompanying
+ / file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ /]
+
+[section External Locking -- `strict_lock` and `externally_locked` classes]
+
+
+[note This tutorial is an adaptation of the paper of Andrei Alexandrescu "Multithreading and the C++ Type System"
+to the Boost library.]
+
+[/
+[section Internal locking]
+
+Consider, for example, modeling a bank account class that supports simultaneous deposits and withdrawals from multiple locations (arguably the "Hello, World" of multi-threaded programming). In the code below, guard's constructor locks the passed-in object this, and guard's destructor unlocks this.
+
+ class BankAccount {
+ boost::mutex mtx_; // explicit mutex declaration
+ int balance_;
+ public:
+ void Deposit(int amount) {
+ boost::lock_guard<boost::mutex> guard(mtx_);
+ balance_ += amount;
+ }
+ void Withdraw(int amount) {
+ boost::lock_guard<boost::mutex> guard(mtx_);
+ balance_ -= amount;
+ }
+ int GetBalance() {
+ boost::lock_guard<boost::mutex> guard(mtx_);
+ return balance_;
+ }
+ };
+
+The object-level locking idiom doesn't cover the entire richness of a threading model. For example, the model above is quite deadlock-prone when you try to coordinate multi-object transactions. Nonetheless, object-level locking is useful in many cases, and in combination with other mechanisms can provide a satisfactory solution to many threaded access problems in object-oriented programs.
+
+[endsect]
+
+[section Internal and external locking]
+
+The BankAccount class above uses internal locking. Basically, a class that uses internal locking guarantees that any concurrent calls to its public member functions don't corrupt an instance of that class. This is typically ensured by having each public member function acquire a lock on the object upon entry. This way, for any given object of that class, there can be only one member function call active at any moment, so the operations are nicely serialized.
+
+This approach is reasonably easy to implement and has an attractive simplicity. Unfortunately, "simple" might sometimes morph into "simplistic."
+
+Internal locking is insufficient for many real-world synchronization tasks. Imagine that you want to implement an ATM withdrawal transaction with the BankAccount class. The requirements are simple. The ATM transaction consists of two withdrawals-one for the actual money and one for the $2 commission. The two withdrawals must appear in strict sequence; that is, no other transaction can exist between them.
+
+The obvious implementation is erratic:
+
+ void ATMWithdrawal(BankAccount& acct, int sum) {
+ acct.Withdraw(sum);
+ // preemption possible
+ acct.Withdraw(2);
+ }
+
+The problem is that between the two calls above, another thread can perform another operation on the account, thus breaking the second design requirement.
+
+In an attempt to solve this problem, let's lock the account from the outside during the two operations:
+
+ void ATMWithdrawal(BankAccount& acct, int sum) {
+ boost::lock_guard<boost::mutex> guard(acct.mtx_); // mtx_ field is private
+ acct.Withdraw(sum);
+ acct.Withdraw(2);
+ }
+
+
+Notice that the code above doesn't compiles, the `mtx_` field is private.
+We have two possibilities:
+
+* make `mtx_` public which seams odd
+* make the `BankAccount` lockable by adding the lock/unlock functions
+
+We can add these functions explicitly
+
+ class BankAccount {
+ boost::mutex mtx_;
+ int balance_;
+ public:
+ void Deposit(int amount) {
+ boost::lock_guard<boost::mutex> guard(mtx_);
+ balance_ += amount;
+ }
+ void Withdraw(int amount) {
+ boost::lock_guard<boost::mutex> guard(mtx_);
+ balance_ -= amount;
+ }
+ void lock() {
+ mtx_.lock();
+ }
+ void unlock() {
+ mtx_.unlock();
+ }
+ };
+
+or inheriting from a class which add these lockable functions.
+
+The `basic_lockable_adapter` class helps to define the `BankAccount` class as
+
+ class BankAccount
+ : public basic_lockable_adapter<thread_mutex>
+ {
+ int balance_;
+ public:
+ void Deposit(int amount) {
+ boost::lock_guard<BankAccount> guard(*this);
+ // boost::lock_guard<boost::mutex> guard(*this->mutex());
+ balance_ += amount;
+ }
+ void Withdraw(int amount) {
+ boost::lock_guard<BankAccount> guard(*this);
+ // boost::lock_guard<boost::mutex> guard(*this->mutex());
+ balance_ -= amount;
+ }
+ int GetBalance() {
+ boost::lock_guard<BankAccount> guard(*this);
+ // boost::lock_guard<boost::mutex> guard(*this->mutex());
+ return balance_;
+ }
+ };
+
+
+
+and the code that does not compiles becomes
+
+ void ATMWithdrawal(BankAccount& acct, int sum) {
+ // boost::lock_guard<boost::mutex> guard(*acct.mutex());
+ boost::lock_guard<BankAccount> guard(acct);
+ acct.Withdraw(sum);
+ acct.Withdraw(2);
+ }
+
+Notice that now acct is being locked by Withdraw after it has already been locked by guard. When running such code, one of two things happens.
+
+* Your mutex implementation might support the so-called recursive mutex semantics. This means that the same thread can lock the same mutex several times successfully. In this case, the implementation works but has a performance overhead due to unnecessary locking. (The locking/unlocking sequence in the two Withdraw calls is not needed but performed anyway-and that costs time.)
+* Your mutex implementation might not support recursive locking, which means that as soon as you try to acquire it the second time, it blocks-so the ATMWithdrawal function enters the dreaded deadlock.
+
+As `boost::mutex` is not recursive, we need to use its recursive version `boost::recursive_mutex`.
+
+ class BankAccount
+ : public basic_lockable_adapter<recursive_mutex>
+ {
+
+ // ...
+ };
+
+The caller-ensured locking approach is more flexible and the most efficient, but very dangerous. In an implementation using caller-ensured locking, BankAccount still holds a mutex, but its member functions don't manipulate it at all. Deposit and Withdraw are not thread-safe anymore. Instead, the client code is responsible for locking BankAccount properly.
+
+ class BankAccount
+ : public basic_lockable_adapter<boost:mutex> {
+ int balance_;
+ public:
+ void Deposit(int amount) {
+ balance_ += amount;
+ }
+ void Withdraw(int amount) {
+ balance_ -= amount;
+ }
+ };
+
+Obviously, the caller-ensured locking approach has a safety problem. BankAccount's implementation code is finite, and easy to reach and maintain, but there's an unbounded amount of client code that manipulates BankAccount objects. In designing applications, it's important to differentiate between requirements imposed on bounded code and unbounded code. If your class makes undue requirements on unbounded code, that's usually a sign that encapsulation is out the window.
+
+To conclude, if in designing a multi-threaded class you settle on internal locking, you expose yourself to inefficiency or deadlocks. On the other hand, if you rely on caller-provided locking, you make your class error-prone and difficult to use. Finally, external locking completely avoids the issue by leaving it all to the client code.
+[endsect]
+]
+[section Locks as Permits]
+
+So what to do? Ideally, the BankAccount class should do the following:
+
+* Support both locking models (internal and external).
+* Be efficient; that is, use no unnecessary locking.
+* Be safe; that is, BankAccount objects cannot be manipulated without appropriate locking.
+
+Let's make a worthwhile observation: Whenever you lock a BankAccount, you do so by using a `lock_guard<BankAccount>` object. Turning this statement around, wherever there's a `lock_guard<BankAccount>`, there's also a locked `BankAccount` somewhere. Thus, you can think of-and use-a `lock_guard<BankAccount>` object as a permit. Owning a `lock_guard<BankAccount>` gives you rights to do certain things. The `lock_guard<BankAccount>` object should not be copied or aliased (it's not a transmissible permit).
+
+# As long as a permit is still alive, the `BankAccount` object stays locked.
+# When the `lock_guard<BankAccount>` is destroyed, the `BankAccount`'s mutex is released.
+
+The net effect is that at any point in your code, having access to a `lock_guard<BankAccount>` object guarantees that a `BankAccount` is locked. (You don't know exactly which `BankAccount` is locked, however-an issue that we'll address soon.)
+
+For now, let's make a couple of enhancements to the `lock_guard` class template defined in Boost.Thread.
+We'll call the enhanced version `strict_lock`. Essentially, a `strict_lock`'s role is only to live on the stack as an automatic variable.
+`strict_lock` must adhere to a non-copy and non-alias policy.
+`strict_lock` disables copying by making the copy constructor and the assignment operator private.
+While we're at it, let's disable operator new and operator delete;
+`strict_lock` are not intended to be allocated on the heap.
+`strict_lock` avoids aliasing by using a slightly less orthodox and less well-known technique: disable address taking.
+
+
+ template <typename Lockable>
+ class strict_lock {
+ public:
+ typedef Lockable lockable_type;
+
+
+ explicit strict_lock(lockable_type& obj) : obj_(obj) {
+ obj.lock(); // locks on construction
+ }
+ strict_lock() = delete;
+ strict_lock(strict_lock const&) = delete;
+ strict_lock& operator=(strict_lock const&) = delete;
+
+ ~strict_lock() { obj_.unlock(); } // unlocks on destruction
+
+ bool owns_lock(mutex_type const* l) const noexcept // strict lockers specific function
+ {
+ return l == &obj_;
+ }
+ private:
+ lockable_type& obj_;
+ };
+
+Silence can be sometimes louder than words-what's forbidden to do with a `strict_lock` is as important as what you can do. Let's see what you can and what you cannot do with a `strict_lock` instantiation:
+
+* You can create a `strict_lock<T>` only starting from a valid T object. Notice that there is no other way you can create a `strict_lock<T>`.
+
+ BankAccount myAccount("John Doe", "123-45-6789");
+ strict_locerk<BankAccount> myLock(myAccount); // ok
+
+* You cannot copy `strict_lock`s to one another. In particular, you cannot pass `strict_lock`s by value to functions or have them returned by functions:
+
+ extern strict_lock<BankAccount> Foo(); // compile-time error
+ extern void Bar(strict_lock<BankAccount>); // compile-time error
+
+* However, you still can pass `strict_lock`s by reference to and from functions:
+
+ // ok, Foo returns a reference to strict_lock<BankAccount>
+ extern strict_lock<BankAccount>& Foo();
+ // ok, Bar takes a reference to strict_lock<BankAccount>
+ extern void Bar(strict_lock<BankAccount>&);
+
+* You cannot allocate a `strict_lock` on the heap. However, you still can put `strict_lock`s on the heap if they're members of a class.
+
+ strict_lock<BankAccount>* pL =
+ new strict_lock<BankAccount>(myAcount); //error!
+ // operator new is not accessible
+ class Wrapper {
+ strict_lock memberLock_;
+ ...
+ };
+ Wrapper* pW = new Wrapper; // ok
+
+(Making `strict_lock` a member variable of a class is not recommended. Fortunately, disabling copying and default construction makes `strict_lock` quite an unfriendly member variable.)
+
+* You cannot take the address of a `strict_lock` object. This interesting feature, implemented by disabling unary operator&, makes it very unlikely to alias a `strict_lock` object. Aliasing is still possible by taking references to a `strict_lock`:
+
+ strict_lock<BankAccount> myLock(myAccount); // ok
+ strict_lock<BankAccount>* pAlias = &myLock; // error!
+ // strict_lock<BankAccount>::operator& is not accessible
+ strict_lock<BankAccount>& rAlias = myLock; // ok
+
+Fortunately, references don't engender as bad aliasing as pointers because they're much less versatile (references cannot be copied or reseated).
+
+* You can even make `strict_lock` final; that is, impossible to derive from. This task is left in the form of an exercise to the reader.
+
+All these rules were put in place with one purpose-enforcing that owning a `strict_lock<T>` is a reasonably strong guarantee that
+
+# you locked a T object, and
+# that object will be unlocked at a later point.
+
+Now that we have such a strict `strict_lock`, how do we harness its power in defining a safe, flexible interface for BankAccount? The idea is as follows:
+
+* Each of BankAccount's interface functions (in our case, Deposit and Withdraw) comes in two overloaded variants.
+* One version keeps the same signature as before, and the other takes an additional argument of type `strict_lock<BankAccount>`. The first version is internally locked; the second one requires external locking. External locking is enforced at compile time by requiring client code to create a `strict_lock<BankAccount>` object.
+* BankAccount avoids code bloating by having the internal locked functions forward to the external locked functions, which do the actual job.
+
+A little code is worth 1,000 words, a (hacked into) saying goes, so here's the new BankAccount class:
+
+ class BankAccount
+ : public basic_lockable_adapter<boost:recursive_mutex>
+ {
+ int balance_;
+ public:
+ void Deposit(int amount, strict_lock<BankAccount>&) {
+ // Externally locked
+ balance_ += amount;
+ }
+ void Deposit(int amount) {
+ strict_lock<boost:mutex> guard(*this); // Internally locked
+ Deposit(amount, guard);
+ }
+ void Withdraw(int amount, strict_lock<BankAccount>&) {
+ // Externally locked
+ balance_ -= amount;
+ }
+ void Withdraw(int amount) {
+ strict_lock<boost:mutex> guard(*this); // Internally locked
+ Withdraw(amount, guard);
+ }
+ };
+
+Now, if you want the benefit of internal locking, you simply call Deposit(int) and Withdraw(int). If you want to use external locking, you lock the object by constructing a `strict_lock<BankAccount>` and then you call `Deposit(int, strict_lock<BankAccount>&)` and `Withdraw(int, strict_lock<BankAccount>&)`. For example, here's the `ATMWithdrawal` function implemented correctly:
+
+ void ATMWithdrawal(BankAccount& acct, int sum) {
+ strict_lock<BankAccount> guard(acct);
+ acct.Withdraw(sum, guard);
+ acct.Withdraw(2, guard);
+ }
+
+This function has the best of both worlds-it's reasonably safe and efficient at the same time.
+
+It's worth noting that `strict_lock` being a template gives extra safety compared to a straight polymorphic approach. In such a design, BankAccount would derive from a Lockable interface. `strict_lock` would manipulate Lockable references so there's no need for templates. This approach is sound; however, it provides fewer compile-time guarantees. Having a `strict_lock` object would only tell that some object derived from Lockable is currently locked. In the templated approach, having a `strict_lock<BankAccount>` gives a stronger guarantee-it's a `BankAccount` that stays locked.
+
+There's a weasel word in there-I mentioned that ATMWithdrawal is reasonably safe. It's not really safe because there's no enforcement that the `strict_lock<BankAccount>` object locks the appropriate BankAccount object. The type system only ensures that some BankAccount object is locked. For example, consider the following phony implementation of ATMWithdrawal:
+
+ void ATMWithdrawal(BankAccount& acct, int sum) {
+ BankAccount fakeAcct("John Doe", "123-45-6789");
+ strict_lock<BankAccount> guard(fakeAcct);
+ acct.Withdraw(sum, guard);
+ acct.Withdraw(2, guard);
+ }
+
+This code compiles warning-free but obviously doesn't do the right thing-it locks one account and uses another.
+
+It's important to understand what can be enforced within the realm of the C++ type system and what needs to be enforced at runtime. The mechanism we've put in place so far ensures that some BankAccount object is locked during the call to `BankAccount::Withdraw(int, strict_lock<BankAccount>&)`. We must enforce at runtime exactly what object is locked.
+
+If our scheme still needs runtime checks, how is it useful? An unwary or malicious programmer can easily lock the wrong object and manipulate any BankAccount without actually locking it.
+
+First, let's get the malice issue out of the way. C is a language that requires a lot of attention and discipline from the programmer. C++ made some progress by asking a little less of those, while still fundamentally trusting the programmer. These languages are not concerned with malice (as Java is, for example). After all, you can break any C/C++ design simply by using casts "appropriately" (if appropriately is an, er, appropriate word in this context).
+
+The scheme is useful because the likelihood of a programmer forgetting about any locking whatsoever is much greater than the likelihood of a programmer who does remember about locking, but locks the wrong object.
+
+Using `strict_lock` permits compile-time checking of the most common source of errors, and runtime checking of the less frequent problem.
+
+Let's see how to enforce that the appropriate BankAccount object is locked. First, we need to add a member function to the `strict_lock` class template.
+The `bool strict_lock<T>::owns_lock(Loclable*)` function returns a reference to the locked object.
+
+ template <class Lockable> class strict_lock {
+ ... as before ...
+ public:
+ bool owns_lock(Lockable* mtx) const { return mtx==&obj_; }
+ };
+
+Second, BankAccount needs to use this function compare the locked object against this:
+
+ class BankAccount {
+ : public basic_lockable_adapter<boost::recursive_mutex>
+ int balance_;
+ public:
+ void Deposit(int amount, strict_lock<BankAccount>& guard) {
+ // Externally locked
+ if (!guard.owns_lock(*this))
+ throw "Locking Error: Wrong Object Locked";
+ balance_ += amount;
+ }
+ // ...
+ };
+
+The overhead incurred by the test above is much lower than locking a recursive mutex for the second time.
+
+[endsect]
+
+[section Improving External Locking]
+
+Now let's assume that BankAccount doesn't use its own locking at all, and has only a thread-neutral implementation:
+
+ class BankAccount {
+ int balance_;
+ public:
+ void Deposit(int amount) {
+ balance_ += amount;
+ }
+ void Withdraw(int amount) {
+ balance_ -= amount;
+ }
+ };
+
+Now you can use BankAccount in single-threaded and multi-threaded applications alike, but you need to provide your own synchronization in the latter case.
+
+Say we have an AccountManager class that holds and manipulates a BankAccount object:
+
+ class AccountManager
+ : public basic_lockable_adapter<boost::mutex>
+ {
+ BankAccount checkingAcct_;
+ BankAccount savingsAcct_;
+ ...
+ };
+
+Let's also assume that, by design, AccountManager must stay locked while accessing its BankAccount members. The question is, how can we express this design constraint using the C++ type system? How can we state "You have access to this BankAccount object only after locking its parent AccountManager object"?
+
+The solution is to use a little bridge template `externally_locked` that controls access to a BankAccount.
+
+ template <typename T, typename Lockable>
+ class externally_locked {
+ BOOST_CONCEPT_ASSERT((LockableConcept<Lockable>));
+
+ public:
+ externally_locked(T& obj, Lockable& lockable)
+ : obj_(obj)
+ , lockable_(lockable)
+ {}
+
+ externally_locked(Lockable& lockable)
+ : obj_()
+ , lockable_(lockable)
+ {}
+
+ T& get(strict_lock<Lockable>& lock) {
+
+ #ifndef BOOST_THREAD_EXTERNALLY_LOCKED_DONT_CHECK_SAME // define BOOST_THREAD_EXTERNALLY_LOCKED_DONT_CHECK_SAME if you don't want to check locker check the same lockable
+ if (!lock.is_locking(&lockable_)) throw lock_error(); run time check throw if not locks the same
+ #endif
+ return obj_;
+ }
+ void set(const T& obj, Lockable& lockable) {
+ obj_ = obj;
+ lockable_=lockable;
+ }
+ private:
+ T obj_;
+ Lockable& lockable_;
+ };
+
+`externally_locked` cloaks an object of type T, and actually provides full access to that object through the get and set member functions, provided you pass a reference to a `strict_lock<Owner>` object.
+
+Instead of making `checkingAcct_` and `savingsAcct_` of type `BankAccount`, `AccountManager` holds objects of type `externally_locked<BankAccount, AccountManager>`:
+
+ class AccountManager
+ : public basic_lockable_adapter<thread_mutex>
+ {
+ public:
+ typedef basic_lockable_adapter<thread_mutex> lockable_base_type;
+ AccountManager()
+ : checkingAcct_(*this)
+ , savingsAcct_(*this)
+ {}
+ inline void Checking2Savings(int amount);
+ inline void AMoreComplicatedChecking2Savings(int amount);
+ private:
+
+ externally_locked<BankAccount, AccountManager> checkingAcct_;
+ externally_locked<BankAccount, AccountManager> savingsAcct_;
+ };
+
+The pattern is the same as before - to access the BankAccount object cloaked by `checkingAcct_`, you need to call `get`. To call `get`, you need to pass it a `strict_lock<AccountManager>`. The one thing you have to take care of is to not hold pointers or references you obtained by calling `get`. If you do that, make sure that you don't use them after the strict_lock has been destroyed. That is, if you alias the cloaked objects, you're back from "the compiler takes care of that" mode to "you must pay attention" mode.
+
+Typically, you use `externally_locked` as shown below. Suppose you want to execute an atomic transfer from your checking account to your savings account:
+
+ void AccountManager::Checking2Savings(int amount) {
+ strict_lock<AccountManager> guard(*this);
+ checkingAcct_.get(guard).Withdraw(amount);
+ savingsAcct_.get(guard).Deposit(amount);
+ }
+
+We achieved two important goals. First, the declaration of `checkingAcct_` and `savingsAcct_` makes it clear to the code reader that that variable is protected by a lock on an AccountManager. Second, the design makes it impossible to manipulate the two accounts without actually locking a BankAccount. `externally_locked` is what could be called active documentation.
+
+[endsect]
+
+[section Allowing other strict locks]
+
+Now imagine that the AccountManager function needs to take a `unique_lock` in order to reduce the critical regions. And at some time it needs to access to the `checkingAcct_`. As `unique_lock` is not a strict lock the following code doesn't compiles:
+
+ void AccountManager::AMoreComplicatedChecking2Savings(int amount) {
+ unique_lock<AccountManager> guard(*this, defer_lock);
+ if (some_condition()) {
+ guard.lock();
+ }
+ checkingAcct_.get(guard).Withdraw(amount); // COMPILE ERROR
+ savingsAcct_.get(guard).Deposit(amount); // COMPILE ERROR
+ do_something_else();
+ }
+
+We need a way to transfer the ownership from the `unique_lock` to a `strict_lock` the time we are working with `savingsAcct_` and then restore the ownership on `unique_lock`.
+
+ void AccountManager::AMoreComplicatedChecking2Savings(int amount) {
+ unique_lock<AccountManager> guard(*this, defer_lock);
+ if (some_condition()) {
+ guard1.lock();
+ }
+ {
+ strict_lock<AccountManager> guard(guard1);
+ checkingAcct_.get(guard).Withdraw(amount);
+ savingsAcct_.get(guard).Deposit(amount);
+ }
+ guard1.unlock();
+ }
+
+In order to make this code compilable we need to store either a Lockable or a `unique_lock<Lockable>` reference depending on the constructor. Store which kind of reference we have stored,and in the destructor call either to the Lockable `unlock` or restore the ownership.
+
+This seams too complicated to me. Another possibility is to define a nested strict lock class. The drawback is that instead of having only one strict lock we have two and we need either to duplicate every function taking a `strict\_lock` or make these function templates functions. The problem with template functions is that we don't profit anymore of the C++ type system. We must add some static metafunction that check that the Locker parameter is a strict lock. The problem is that we can not really check this or can we?. The `is_strict_lock` metafunction must be specialized by the strict lock developer. We need to belive it "sur parolle". The advantage is that now we can manage with more than two strict locks without changing our code. Ths is really nice.
+
+Now we need to state that both classes are `strict_lock`s.
+
+ template <typename Locker>
+ struct is_strict_lock : mpl::false_ {};
+
+ template <typename Lockable>
+ struct is_strict_lock<strict_lock<Lockable> > : mpl::true_ {}
+
+ template <typename Locker>
+ struct is_strict_lock<nested_strict_lock<Locker> > : mpl::true_ {}
+
+
+Well let me show how this `nested_strict_lock` class looks like and the impacts on the `externally_locked` class and the `AccountManager::AMoreComplicatedFunction` function.
+
+First `nested_strict_lock` class will store on a temporary lock the `Locker`, and transfer the lock ownership on the constructor. On destruction he will restore the ownership. Note also that the Locker needs to have already a reference to the mutex otherwise an exception is thrown and the use of the `lock_traits`.
+
+ template <typename Locker >
+ class nested_strict_lock
+ {
+ BOOST_CONCEPT_ASSERT((MovableLockerConcept<Locker>));
+ public:
+ typedef typename lockable_type<Locker>::type lockable_type;
+ typedef typename syntactic_lock_traits<lockable_type>::lock_error lock_error;
+
+ nested_strict_lock(Locker& lock)
+ : lock_(lock) // Store reference to locker
+ , tmp_lock_(lock.move()) // Move ownership to temporaty locker
+ {
+ #ifndef BOOST_THREAD_STRCIT_LOCKER_DONT_CHECK_OWNERSHIP // Define BOOST_THREAD_EXTERNALLY_LOCKED_DONT_CHECK_OWNERSHIP if you don't want to check locker ownership
+ if (tmp_lock_.mutex()==0) {
+ lock_=tmp_lock_.move(); // Rollback for coherency purposes
+ throw lock_error();
+ }
+ #endif
+ if (!tmp_lock_) tmp_lock_.lock(); // ensures it is locked
+ }
+ ~nested_strict_lock() {
+ lock_=tmp_lock_.move(); // Move ownership to nesting locker
+ }
+ typedef bool (nested_strict_lock::*bool_type)() const;
+ operator bool_type() const { return &nested_strict_lock::owns_lock; }
+ bool operator!() const { return false; }
+ bool owns_lock() const { return true; }
+ const lockable_type* mutex() const { return tmp_lock_.mutex(); }
+ bool is_locking(lockable_type* l) const { return l==mutex(); }
+
+ BOOST_ADRESS_OF_DELETE(nested_strict_lock)
+ BOOST_HEAP_ALLOCATEION_DELETE(nested_strict_lock)
+ BOOST_DEFAULT_CONSTRUCTOR_DELETE(nested_strict_lock) 8
+ BOOST_COPY_CONSTRUCTOR_DELETE(nested_strict_lock) 9
+ BOOST_COPY_ASSIGNEMENT_DELETE(nested_strict_lock) 10
+
+ private:
+ Locker& lock_;
+ Locker tmp_lock_;
+ };
+
+The `externally_locked` get function is now a template function taking a Locker as parameters instead of a `strict_lock`.
+We can add test in debug mode that ensure that the Lockable object is locked.
+
+ template <typename T, typename Lockable>
+ class externally_locked {
+ public:
+ // ...
+ template <class Locker>
+ T& get(Locker& lock) {
+ BOOST_CONCEPT_ASSERT((StrictLockerConcept<Locker>));
+
+ BOOST_STATIC_ASSERT((is_strict_lock<Locker>::value)); // locker is a strict locker "sur parolle"
+ BOOST_STATIC_ASSERT((is_same<Lockable,
+ typename lockable_type<Locker>::type>::value)); // that locks the same type
+ #ifndef BOOST_THREAD_EXTERNALLY_LOCKED_DONT_CHECK_OWNERSHIP // define BOOST_THREAD_EXTERNALLY_LOCKED_NO_CHECK_OWNERSHIP if you don't want to check locker ownership
+ if (! lock ) throw lock_error(); // run time check throw if no locked
+ #endif
+ #ifndef BOOST_THREAD_EXTERNALLY_LOCKED_DONT_CHECK_SAME
+ if (!lock.is_locking(&lockable_)) throw lock_error();
+ #endif
+ return obj_;
+ }
+ };
+
+The `AccountManager::AMoreComplicatedFunction` function needs only to replace the `strict_lock` by a `nested_strict_lock`.
+
+ void AccountManager::AMoreComplicatedChecking2Savings(int amount) {
+ unique_lock<AccountManager> guard1(*this);
+ if (some_condition()) {
+ guard1.lock();
+ }
+ {
+ nested_strict_lock<unique_lock<AccountManager> > guard(guard1);
+ checkingAcct_.get(guard).Withdraw(amount);
+ savingsAcct_.get(guard).Deposit(amount);
+ }
+ guard1.unlock();
+ }
+
+[endsect]
+
+[endsect]
+
+
Added: trunk/libs/thread/doc/internal_locking.qbk
==============================================================================
--- (empty file)
+++ trunk/libs/thread/doc/internal_locking.qbk 2012-12-16 17:40:10 EST (Sun, 16 Dec 2012)
@@ -0,0 +1,450 @@
+[/
+ / Copyright (c) 2008 Vicente J. Botet Escriba
+ /
+ / Distributed under the Boost Software License, Version 1.0. (See accompanying
+ / file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ /]
+
+
+[section Internal Locking]
+[note This tutorial is an adaptation of chapter Concurrency of the Object-Oriented Programming in the BETA Programming Language and of the paper of Andrei Alexandrescu "Multithreading and the C++ Type System" to the Boost library.]
+[section Concurrent threads of execution]
+
+Consider, for example, modeling a bank account class that supports simultaneous deposits and withdrawals from multiple locations (arguably the "Hello, World" of multithreaded programming).
+
+From here a component is a model of the `Callable` concept.
+
+On C++11 (Boost) concurrent execution of a component is obtained by means of the `std::thread`(`boost::thread`):
+
+ boost::thread thread1(S);
+
+where `S` is a model of `Callable`. The meaning of this expression is that execution of `S()` will take place concurrently with the current thread of execution executing the expression.
+
+The following example includes a bank account of a person (Joe) and two components, one corresponding to a bank agent depositing money in Joe's account, and one representing Joe. Joe will only be withdrawing money from the account:
+
+ class BankAccount;
+
+ BankAccount JoesAccount;
+
+ void bankAgent()
+ {
+ for (int i =10; i>0; --i) {
+ //...
+ JoesAccount.Deposit(500);
+ //...
+ }
+ }
+
+ void Joe() {
+ for (int i =10; i>0; --i) {
+ //...
+ int myPocket = JoesAccount.Withdraw(100);
+ std::cout << myPocket << std::endl;
+ //...
+ }
+ }
+
+ int main() {
+ //...
+ boost::thread thread1(bankAgent); // start concurrent execution of bankAgent
+ boost::thread thread2(Joe); // start concurrent execution of Joe
+ thread1.join();
+ thread2.join();
+ return 0;
+ }
+
+From time to time, the `bankAgent` will deposit $500 in `JoesAccount`. Joe will similarly withdraw $100 from his account. These sentences describe that the bankAgent and Joe are executed concurrently.
+
+The above example works well as long as the bankAgent and Joe doesn't access JoesAccount at the same time. There is, however, no guarantee that this will not happen. We may use a mutex to guarantee exclusive access to each bank.
+
+ class BankAccount {
+ boost::mutex mtx_;
+ int balance_;
+ public:
+ void Deposit(int amount) {
+ mtx_.lock();
+ balance_ += amount;
+ mtx_.unlock();
+ }
+ void Withdraw(int amount) {
+ mtx_.lock();
+ balance_ -= amount;
+ mtx_.unlock();
+ }
+ int GetBalance() {
+ mtx_.lock();
+ int b = balance_;
+ mtx_.unlock();
+ return balance_;
+ }
+ };
+
+Execution of the Deposit and Withdraw operations will no longer be able to make simultaneous access to balance.
+
+Mutex is a simple and basic mechanism for obtaining synchronization. In the above example it is relatively easy to be convinced that the synchronization works correctly (in the absence of exception). In a system with several concurrent objects and several shared objects, it may be difficult to describe synchronization by means of mutexes. Programs that make heavy use of mutexes may be difficult to read and write. Instead, we shall introduce a number of generic classes for handling more complicated forms of synchronization and communication.
+
+With the RAII idiom we can simplify a lot this using the scoped locks. In the code below, guard's constructor locks the passed-in object this, and guard's destructor unlocks this.
+
+ class BankAccount {
+ boost::mutex mtx_; // explicit mutex declaration
+ int balance_;
+ public:
+ void Deposit(int amount) {
+ boost::lock_guard<boost::mutex> guard(mtx_);
+ balance_ += amount;
+ }
+ void Withdraw(int amount) {
+ boost::lock_guard<boost::mutex> guard(mtx_);
+ balance_ -= amount;
+ }
+ int GetBalance() {
+ boost::lock_guard<boost::mutex> guard(mtx_);
+ return balance_;
+ }
+ };
+
+The object-level locking idiom doesn't cover the entire richness of a threading model. For example, the model above is quite deadlock-prone when you try to coordinate multi-object transactions. Nonetheless, object-level locking is useful in many cases, and in combination with other mechanisms can provide a satisfactory solution to many threaded access problems in object-oriented programs.
+
+The BankAccount class above uses internal locking. Basically, a class that uses internal locking guarantees that any concurrent calls to its public member functions don't corrupt an instance of that class. This is typically ensured by having each public member function acquire a lock on the object upon entry. This way, for any given object of that class, there can be only one member function call active at any moment, so the operations are nicely serialized.
+
+This approach is reasonably easy to implement and has an attractive simplicity. Unfortunately, "simple" might sometimes morph into "simplistic."
+
+Internal locking is insufficient for many real-world synchronization tasks. Imagine that you want to implement an ATM withdrawal transaction with the BankAccount class. The requirements are simple. The ATM transaction consists of two withdrawals-one for the actual money and one for the $2 commission. The two withdrawals must appear in strict sequence; that is, no other transaction can exist between them.
+
+The obvious implementation is erratic:
+
+ void ATMWithdrawal(BankAccount& acct, int sum) {
+ acct.Withdraw(sum);
+ // preemption possible
+ acct.Withdraw(2);
+ }
+
+The problem is that between the two calls above, another thread can perform another operation on the account, thus breaking the second design requirement.
+
+In an attempt to solve this problem, let's lock the account from the outside during the two operations:
+
+ void ATMWithdrawal(BankAccount& acct, int sum) {
+ boost::lock_guard<boost::mutex> guard(acct.mtx_); 1
+ acct.Withdraw(sum);
+ acct.Withdraw(2);
+ }
+
+Notice that the code above doesn't compiles, the `mtx_` field is private.
+We have two possibilities:
+
+* make `mtx_` public which seams odd
+* make the `BankAccount` lockable by adding the lock/unlock functions
+
+We can add these functions explicitly
+
+ class BankAccount {
+ boost::mutex mtx_;
+ int balance_;
+ public:
+ void Deposit(int amount) {
+ boost::lock_guard<boost::mutex> guard(mtx_);
+ balance_ += amount;
+ }
+ void Withdraw(int amount) {
+ boost::lock_guard<boost::mutex> guard(mtx_);
+ balance_ -= amount;
+ }
+ void lock() {
+ mtx_.lock();
+ }
+ void unlock() {
+ mtx_.unlock();
+ }
+ };
+
+or inheriting from a class which add these lockable functions.
+
+The `basic_lockable_adapter` class helps to define the `BankAccount` class as
+
+ class BankAccount
+ : public basic_lockable_adapter<mutex>
+ {
+ int balance_;
+ public:
+ void Deposit(int amount) {
+ boost::lock_guard<BankAccount> guard(*this);
+ balance_ += amount;
+ }
+ void Withdraw(int amount) {
+ boost::lock_guard<BankAccount> guard(*this);
+ balance_ -= amount;
+ }
+ int GetBalance() {
+ boost::lock_guard<BankAccount> guard(*this);
+ return balance_;
+ }
+ };
+
+
+and the code that doesn't compiles becomes
+
+ void ATMWithdrawal(BankAccount& acct, int sum) {
+ boost::lock_guard<BankAccount> guard(acct);
+ acct.Withdraw(sum);
+ acct.Withdraw(2);
+ }
+
+Notice that now acct is being locked by Withdraw after it has already been locked by guard. When running such code, one of two things happens.
+
+* Your mutex implementation might support the so-called recursive mutex semantics. This means that the same thread can lock the same mutex several times successfully. In this case, the implementation works but has a performance overhead due to unnecessary locking. (The locking/unlocking sequence in the two Withdraw calls is not needed but performed anyway-and that costs time.)
+* Your mutex implementation might not support recursive locking, which means that as soon as you try to acquire it the second time, it blocks-so the ATMWithdrawal function enters the dreaded deadlock.
+
+As `boost::mutex` is not recursive, we need to use its recursive version `boost::recursive_mutex`.
+
+ class BankAccount
+ : public basic_lockable_adapter<recursive_mutex>
+ {
+
+ // ...
+ };
+
+
+[endsect]
+
+[/
+[section Monitors]
+
+The use of `mutex` and `lockers`, as in `BankAccount`, is a common way of defining objects shared by two or more concurrent components. The basic_lockable_adapter class was a first step.
+We shall therefore introduce an abstraction that makes it easier to define such objects.
+The following class describes a so-called monitor pattern.
+
+ template <
+ typename Lockable=mutex
+ >
+ class basic_monitor : protected basic_lockable_adapter<Lockable> { // behaves like an BasicLockable for the derived classes
+ protected:
+ typedef unspecified synchronizer; // is an strict lock guard
+ };
+
+[/shared_monitor]
+[/monitor]
+
+A basic_monitor object behaves like a `BasicLockable` object but only for the inheriting classes.
+Protected inheritance from lockable_adapter provide to all the derived classes all BasicLockable operations. In addition has a protected nested class, synchronizer, used when defining the monitor operations to synchronize the access critical regions. The BankAccount may be described using Monitor in the following way:
+
+ class BankAccount : protected basic_monitor<>
+ {
+ protected:
+ int balance_;
+ public:
+ BankAccount() : balance_(0) {}
+ BankAccount(const BankAccount &rhs) {
+ synchronizer _(*rhs.mutex());
+ balance_=rhs.balance_;
+ }
+
+ BankAccount& operator=(BankAccount &rhs)
+ {
+ if(&rhs == this) return *this;
+
+ int balance=0;
+ {
+ synchronizer _(*rhs.mutex());
+ balance=rhs.balance_;
+ }
+ synchronizer _(*this->mutex());
+ balance_=balance;
+ return *this;
+ }
+
+ void Deposit(int amount) {
+ synchronizer _(*this->mutex());
+ balance_ += amount;
+ }
+ int Withdraw(int amount) {
+ synchronizer _(*this->mutex());
+ balance_ -= amount;
+ return amount;
+ }
+ int GetBalance() {
+ synchronizer _(*this->mutex());
+ return balance_;
+ }
+ };
+
+In the following, a monitor means some sub-class of monitor. A synchronized operation means an operation using the synchronizer guard defined within some monitor. Monitor is one example of a high-level concurrency abstraction that can be defined by means of mutexes.
+
+
+[section Monitor Conditions]
+
+It may happen that a component executing an entry operation of a monitor is unable to continue execution due to some condition not being fulfilled. Consider, for instance, a bounded buffer of characters. Such a buffer may be implemented as a monitor with two operations Push and Pull: the Puss operation cannot be executed if the buffer is full, and the Pull operation cannot be executed if the buffer is empty. A sketch of such a buffer monitor may look as
+follows:
+
+ class sync_buffer {
+ boost::mutex mtx_; 1
+ public:
+ ...
+ bool full() { return in_==out_; }
+ bool empty() { return in_==(out_%size)+1; }
+ void push(T& v) {
+ // wait if buffer is full
+ data_[in_]=v;
+ in_ = (in_% size)+1;
+ }
+ T pull() {
+ // wait if buffer is empty
+ out_ = (out_% size)+1;
+ return data_[out_];
+ }
+ };
+
+The meaning of a wait is that the calling component is delayed until the condition becomes true. We can do that using Boost.Thread condition variables like:
+
+ template <typename T, unsigned size>
+ class sync_buffer
+ {
+ typedef boost::mutex mutex_type;
+ typedef boost::condition_variable condition_type;
+ typedef boost::unique_lock<mutex_type> unique_lock_type;
+ mutex_type mtx_;
+ condition_type not_full_;
+ condition_type not_empty_;
+
+ T data_[size+1];
+ unsigned in_, out_;
+
+ public:
+ sync_buffer():in_(0), out_(0) {}
+
+ bool full() { return out_==(in_+1)%(size+1); }
+ bool empty() { return out_==in_; }
+
+ unsigned get_in() {return in_;}
+ unsigned get_out() {return out_;}
+ void push(T v) {
+ unique_lock_type guard(mtx_); 1
+ while (full()) { 2
+ not_full_.wait(guard);
+ }
+ data_[in_]=v;
+ in_ = (in_+1)% (size+1);
+ not_empty_.notify_one(); 3
+ }
+
+ T pull() {
+ unique_lock_type guard(mtx_); 4
+ while (empty()) { 5
+ not_empty_.wait(guard);
+ }
+ unsigned idx = out_;
+ out_ = (out_+1)% (size+1);
+ not_full_.notify_one(); 6
+ return data_[idx];
+ }
+ };
+
+The Monitor class replace the nested synchronizer unique_lock with the class `condition_unique_lock` for this purpose:
+
+ template <
+ typename Lockable,
+ class Condition=condition_safe<typename best_condition<Lockable>::type >
+ , typename ScopeTag=typename scope_tag<Lockable>::type
+ >
+ class condition_unique_lock
+ : protected unique_lock<Lockable,ScopeTag>
+ {
+ BOOST_CONCEPT_ASSERT((LockableConcept<Lockable>));
+ public:
+ typedef Lockable lockable_type;
+ typedef Condition condition;
+
+ explicit condition_unique_lock(lockable_type& obj); 1
+ condition_unique_lock(lockable_type& obj, condition &cond); 2
+ template <typename Predicate>
+ condition_unique_lock(lockable_type& obj, condition &cond, Predicate pred); 3
+ ~condition_unique_lock() 4
+
+ typedef bool (condition_unique_lock::*bool_type)() const; 5
+ operator bool_type() const; 6
+ bool operator!() const { return false; } 7
+ bool owns_lock() const { return true; } 8
+ bool is_locking(lockable_type* l) const 9
+
+ void relock_on(condition & cond);
+ template<typename Clock, typename Duration>
+ void relock_until(condition & cond, chrono::time_point<Clock, Duration> const& abs_time);
+ template<typename duration_type>
+ void relock_on_for(condition & cond, duration_type const& rel_time);
+
+ template<typename Predicate>
+ void relock_when(condition &cond, Predicate pred);
+ template<typename Predicate>
+ template<typename Clock, typename Duration>
+ void relock_when_until(condition &cond, Predicate pred,
+ chrono::time_point<Clock, Duration> const& abs_time);
+ template<typename Predicate, typename duration_type>
+ void relock_when_for(condition &cond, Predicate pred,
+ duration_type const& rel_time);
+
+ 10
+ };
+
+
+We may now give the complete version of the buffer class. The content of the buffer is: `data_[out_+1], data_[out_+2], ... data_R[in_-1]` where all the indexes are modulo size. The buffer is full if `in_=out_` and it is empty if `in_=(out_+1)%size`.
+
+ template <typename T, unsigned size>
+ class sync_buffer : protected basic_monitor<>
+ {
+ condition not_full_;
+ condition not_empty_;
+
+ T data_[size+1];
+ unsigned in_, out_;
+
+ struct not_full {
+ explicit not_full(sync_buffer &b):that_(b){};
+ bool operator()() const { return !that_.full(); }
+ sync_buffer &that_;
+ };
+ struct not_empty {
+ explicit not_empty(sync_buffer &b):that_(b){};
+ bool operator()() const { return !that_.empty(); }
+ sync_buffer &that_;
+ };
+ public:
+ BOOST_COPY_CONSTRUCTOR_DELETE(sync_buffer) 1
+ BOOST_COPY_ASSIGNEMENT_DELETE(sync_buffer) 2
+ sync_buffer():in_(0), out_(0) {}
+
+ bool full() { return out_==(in_+1)%(size+1); }
+ bool empty() { return out_==in_; }
+
+ unsigned get_in() {return in_;}
+ unsigned get_out() {return out_;}
+
+ void push(T v) {
+ synchronizer _(*this->mutex(), not_full_, not_full(*this)); 3
+ data_[in_]=v;
+ in_ = (in_+1)% (size+1);
+ not_empty_.notify_one(); 4
+ }
+
+ T pull() {
+ synchronizer _(*this->mutex(), not_empty_, not_empty(*this)); 5
+ unsigned idx = out_;
+ out_ = (out_+1)% (size+1);
+ not_full_.notify_one(); 6
+ return data_[idx];
+ }
+ };
+
+Monitors and conditions are useful for describing simple cases of shared objects (by simple we mean a limited use of conditions). If the conditions for delaying a calling component become complicated, the monitor may similarly become difficult to program and read.
+
+[endsect] [/Monitor Conditions]
+
+[endsect] [/Monitors]
+]
+
+[section Synchronized variables]
+[/include synchronized_value.qbk]
+[endsect] [/Synchronized variables]
+
+
+[endsect] [/Internal Locking]
+
+
Modified: trunk/libs/thread/doc/mutex_concepts.qbk
==============================================================================
--- trunk/libs/thread/doc/mutex_concepts.qbk (original)
+++ trunk/libs/thread/doc/mutex_concepts.qbk 2012-12-16 17:40:10 EST (Sun, 16 Dec 2012)
@@ -26,7 +26,7 @@
{
template<typename L>
- class BasicLockable;
+ class BasicLockable; // EXTENSION
}
@@ -80,7 +80,7 @@
[endsect]
-[section:is_basic_lockable `is_basic_lockable` trait]
+[section:is_basic_lockable `is_basic_lockable` trait -- EXTENSION]
// #include <boost/thread/lockable_traits.hpp>
@@ -89,7 +89,7 @@
namespace sync
{
template<typename L>
- class is_basic_lockable;
+ class is_basic_lockable;// EXTENSION
}
}
@@ -130,7 +130,7 @@
]
[endsect]
-[section:is_lockable `is_lockable` trait]
+[section:is_lockable `is_lockable` trait -- EXTENSION]
// #include <boost/thread/lockable_traits.hpp>
namespace boost
@@ -138,7 +138,7 @@
namespace sync
{
template<typename L>
- class is_lockable;
+ class is_lockable;// EXTENSION
}
}
@@ -152,7 +152,7 @@
The user could require that the mutex passed to an algorithm is a recursive one. Whether a lockable is recursive or not can not be checked using template meta-programming. This is the motivation for the following trait.
-[section:is_recursive_mutex_sur_parolle `is_recursive_mutex_sur_parolle` trait]
+[section:is_recursive_mutex_sur_parolle `is_recursive_mutex_sur_parolle` trait -- EXTENSION]
// #include <boost/thread/lockable_traits.hpp>
@@ -161,11 +161,11 @@
namespace sync
{
template<typename L>
- class is_recursive_mutex_sur_parolle: false_type;
+ class is_recursive_mutex_sur_parolle: false_type; // EXTENSION
template<>
- class is_recursive_mutex_sur_parolle<recursive_mutex>: true_type;
+ class is_recursive_mutex_sur_parolle<recursive_mutex>: true_type; // EXTENSION
template<>
- class is_recursive_mutex_sur_parolle<timed_recursive_mutex>: true_type;
+ class is_recursive_mutex_sur_parolle<timed_recursive_mutex>: true_type; // EXTENSION
}
}
@@ -185,7 +185,7 @@
namespace boost
{
template<typename L>
- class TimedLockable;
+ class TimedLockable; // EXTENSION
}
The __timed_lockable_concept__ refines the __lockable_concept__ to add support for
@@ -279,14 +279,14 @@
[endsect]
-[section:shared_lockable `SharedLockable` Concept]
+[section:shared_lockable `SharedLockable` Concept -- EXTENSION]
// #include <boost/thread/lockable_concepts.hpp>
namespace boost
{
template<typename L>
- class SharedLockable;
+ class SharedLockable; // EXTENSION
}
@@ -431,14 +431,14 @@
[endsect]
-[section:upgrade_lockable `UpgradeLockable` Concept]
+[section:upgrade_lockable `UpgradeLockable` Concept -- EXTENSION]
// #include <boost/thread/lockable_concepts.hpp>
namespace boost
{
template<typename L>
- class UpgradeLockable;
+ class UpgradeLockable; // EXTENSION
}
@@ -948,10 +948,12 @@
template<typename Lockable>
class lock_guard
+ #if ! defined BOOST_NO_CXX11_HDR_INITIALIZER_LIST
template <typename Lockable>
lock_guard<Lockable> make_lock_guard(Lockable& mtx); // EXTENSION
template <typename Lockable>
lock_guard<Lockable> make_lock_guard(Lockable& mtx, adopt_lock_t); // EXTENSION
+ #endif
}
[section:lock_guard Class template `lock_guard`]
@@ -1056,7 +1058,7 @@
[endsect]
[section:lock_concepts Lock Concepts]
-[section:StrictLock StrictLock]
+[section:StrictLock StrictLock -- EXTENSION]
// #include <boost/thread/lock_concepts.hpp>
@@ -1097,19 +1099,19 @@
[endsect] [/ is_strict_lock_sur_parolle]
-[section:mutex `cl.mutex();`]
+[section:owns_lock `cl.owns_lock(m);`]
[variablelist
-[[Return Type:] [`L::mutex_type`]]
-[[Returns:] [A pointer to the `L::mutex_type` object that this lock `l` is locking]]
+[[Return Type:] [`bool`]]
+[[Returns:] [Whether the strict lock is locking the mutex `m`]]
[[Throws:] [Nothing.]]
]
-[endsect] [/ mutex]
+[endsect] [/ owns_lock]
[section Models]
@@ -1138,15 +1140,15 @@
template<typename Mutex>
void swap(unique_lock <Mutex>& lhs, unique_lock <Mutex>& rhs);
template<typename Lockable>
- class shared_lock;
+ class shared_lock; // EXTENSION
template<typename Mutex>
- void swap(shared_lock<Mutex>& lhs,shared_lock<Mutex>& rhs);
+ void swap(shared_lock<Mutex>& lhs,shared_lock<Mutex>& rhs); // EXTENSION
template<typename Lockable>
- class upgrade_lock;
+ class upgrade_lock; // EXTENSION
template<typename Mutex>
- void swap(upgrade_lock <Mutex>& lhs, upgrade_lock <Mutex>& rhs);
+ void swap(upgrade_lock <Mutex>& lhs, upgrade_lock <Mutex>& rhs); // EXTENSION
template <class Mutex>
- class upgrade_to_unique_lock;
+ class upgrade_to_unique_lock; // EXTENSION
}
@@ -1516,7 +1518,7 @@
[endsect]
-[section:shared_lock Class template `shared_lock`]
+[section:shared_lock Class template `shared_lock` - EXTENSION]
// #include <boost/thread/locks.hpp>
// #include <boost/thread/lock_types.hpp>
@@ -1758,7 +1760,7 @@
[endsect]
-[section:upgrade_lock Class template `upgrade_lock`]
+[section:upgrade_lock Class template `upgrade_lock` - EXTENSION]
// #include <boost/thread/locks.hpp>
// #include <boost/thread/lock_types.hpp>
@@ -1944,9 +1946,9 @@
]
-[section:other_locks Other Lock Types]
+[section:other_locks Other Lock Types - EXTENSION]
-[section:strict_lock Strict Lock]
+[section:strict_locks Strict Locks]
// #include <boost/thread/locks.hpp>
// #include <boost/thread/strict_lock.hpp>
@@ -1956,6 +1958,20 @@
template<typename Lockable>
class strict_lock;
+ template <typename Lock>
+ class nested_strict_lock;
+ template <typename Lockable>
+ struct is_strict_lock_sur_parolle<strict_lock<Lockable> >;
+ template <typename Lock>
+ struct is_strict_lock_sur_parolle<nested_strict_lock<Lock> >;
+
+ #if ! defined BOOST_NO_CXX11_HDR_INITIALIZER_LIST
+ template <typename Lockable>
+ strict_lock<Lockable> make_strict_lock(Lockable& mtx);
+ template <typename Lock>
+ nested_strict_lock<Lock> make_nested_strict_lock(Lock& lk);
+ #endif
+
}
[section:strict_lock Class template `strict_lock`]
@@ -1971,7 +1987,7 @@
explicit strict_lock(mutex_type& m_);
~strict_lock();
- mutex_type* mutex() const;
+ bool owns_lock(mutex_type const* l) const noexcept;
};
__strict_lock is a model of __StrictLock.
@@ -2006,9 +2022,307 @@
[endsect]
[endsect]
+
+
+[section:nested_strict_lock Class template `nested_strict_lock`]
+
+ // #include <boost/thread/locks.hpp>
+ // #include <boost/thread/strict_lock.hpp>
+
+ template<typename Lock>
+ class nested_strict_lock
+ {
+ public:
+ typedef BasicLockable mutex_type;
+ explicit nested_strict_lock(Lock& lk),
+ ~nested_strict_lock() noexcept;
+
+ bool owns_lock(mutex_type const* l) const noexcept;
+ };
+
+__nested_strict_lock is a model of __StrictLock.
+
+A nested strict lock is a scoped lock guard ensuring a mutex is locked on its
+scope, by taking ownership of an nesting lock, locking the mutex on construction if not already locked
+and restoring the ownership to the nesting lock on destruction.
+
+
+[heading See also __strict_lock, __unique_lock]
+
+[section:constructor `nested_strict_lock(Lock & lk)`]
+
+[variablelist
+
+[[Requires:] [`lk.mutex() != null_ptr`.]]
+
+[[Effects:] [Stores the reference to the lock parameter `lk` and takes ownership on it.
+If the lock doesn't owns the mutex lock it.
+]]
+
+[[Postcondition:] [`owns_lock(lk.mutex())`.]]
+
+[[Throws:] [
+
+- lock_error when BOOST_THREAD_THROW_IF_PRECONDITION_NOT_SATISFIED is defined and lk.mutex() == null_ptr
+
+- Any exception that @c lk.lock() can throw.
+
+
+]]
+
+]
+
+[endsect]
+
+[section:destructor `~nested_strict_lock() noexcept`]
+
+[variablelist
+
+[[Effects:] [Restores ownership to the nesting lock.]]
+
+]
+
+[endsect]
+
+
+[section:owns_lock `bool owns_lock(mutex_type const* l) const noexcept`]
+
+[variablelist
+
+[[Return:] [Whether if this lock is locking that mutex.]]
+
+]
+
+[endsect]
+
+[endsect]
+
+[section:make_strict_lock Non Member Function `make_strict_lock`]
+
+ template <typename Lockable>
+ strict_lock<Lockable> make_strict_lock(Lockable& m); // EXTENSION
+
+
+[variablelist
+
+[[Returns:] [a strict_lock as if initialized with `{m}`.]]
+
+[[Throws:] [Any exception thrown by the call to [lock_ref_link `m.lock()`].]]
+
+]
+
+
+[endsect]
+
+
+[section:make_nested_strict_lock Non Member Function `make_nested_strict_lock`]
+
+ template <typename Lock>
+ nested_strict_lock<Lock> make_nested_strict_lock(Lock& lk); // EXTENSION
+
+
+[variablelist
+
+[[Returns:] [a nested_strict_lock as if initialized with `{lk}`.]]
+
+[[Throws:] [Any exception thrown by the call to [lock_ref_link `lk.lock()`].]]
+
+]
+
+
+[endsect]
+
+
[endsect]
+[section Externally Locked]
+
+ // #include <boost/thread/externally_locked.hpp>
+ template <class T, typename MutexType = boost::mutex>
+ class externally_locked;
+ template <typename T, typename MutexType>
+ void swap(externally_locked<T, MutexType> & lhs, externally_locked<T, MutexType> & rhs);
+
+[section Template Class `externally_locked`]
+
+ // #include <boost/thread/externally_locked.hpp>
+
+ template <class T, typename MutexType>
+ class externally_locked
+ {
+ //BOOST_CONCEPT_ASSERT(( CopyConstructible<T> ));
+ BOOST_CONCEPT_ASSERT(( BasicLockable<MutexType> ));
+
+ public:
+ typedef MutexType mutex_type;
+
+ externally_locked(mutex_type& mtx, const T& obj);
+ externally_locked(mutex_type& mtx,T&& obj);
+ explicit externally_locked(mutex_type& mtx);
+ externally_locked(externally_locked&& rhs);
+
+ // observers
+ T& get(strict_lock<mutex_type>& lk);
+ const T& get(strict_lock<mutex_type>& lk) const;
+
+ template <class Lock>
+ T& get(nested_strict_lock<Lock>& lk);
+ template <class Lock>
+ const T& get(nested_strict_lock<Lock>& lk) const;
+
+ template <class Lock>
+ T& get(Lock& lk);
+ template <class Lock>
+ T const& get(Lock& lk) const;
+
+ mutex_type* mutex();
+
+ // modifiers
+ void lock();
+ void unlock();
+ bool try_lock();
+ void swap(externally_locked&);
+ };
+
+`externally_locked` is a model of __Lockable, it cloaks an object of type `T`, and actually provides full
+access to that object through the get and set member functions, provided you
+pass a reference to a strict lock object.
+
+Only the specificities respect to __Lockable are described here.
+
+[///////////////////////////////]
+[section:constructor1 `externally_locked(mutex_type&, const T&)`]
+
+ externally_locked(mutex_type& mtx, const T& obj);
+
+[variablelist
+
+[[Requires:] [T is a model of CopyConstructible.]]
+
+[[Effects:] [Constructs an externally locked object copying the cloaked type.]]
+
+[[Throws:] [Any exception thrown by the call to `T(obj)`.]]
+
+]
+
+[endsect]
+[///////////////////////////////]
+[section:constructor2 `externally_locked(mutex_type&, T&&)`]
+
+ externally_locked(mutex_type& mtx,T&& obj);
+
+[variablelist
+
+[[Requires:] [T is a model of Movable.]]
+
+[[Effects:] [Constructs an externally locked object by moving the cloaked type.]]
+
+[[Throws:] [Any exception thrown by the call to `T(obj)`.]]
+
+]
+
+[endsect]
+[///////////////////////////////]
+[section:constructor3 `externally_locked(mutex_type&)`]
+
+ externally_locked(mutex_type& mtx);
+
+[variablelist
+
+[[Requires:] [T is a model of DefaultConstructible.]]
+
+[[Effects:] [Constructs an externally locked object by default constructing the cloaked type.]]
+
+[[Throws:] [Any exception thrown by the call to `T()`.]]
+
+]
+
+[endsect]
+[///////////////////////////////]
+[section:constructor4 `externally_locked(externally_locked&)`]
+
+ externally_locked(externally_locked&& rhs);
+
+[variablelist
+
+[[Requires:] [T is a model of Movable.]]
+
+[[Effects:] [Moves an externally locked object by moving the the cloaked type and copying the mutex reference ]]
+
+[[Throws:] [Any exception thrown by the call to `T(T&&)`.]]
+
+]
+
+[endsect]
+[///////////////////////////////]
+[section:get1 `get(strict_lock<mutex_type>&)`]
+
+ T& get(strict_lock<mutex_type>& lk);
+ const T& get(strict_lock<mutex_type>& lk) const;
+
+[variablelist
+
+[[Requires:] [The `lk` parameter must be locking the associated mutex.]]
+
+[[Returns:] [A reference to the cloaked object ]]
+
+[[Throws:] [__lock_error__ if `BOOST_THREAD_THROW_IF_PRECONDITION_NOT_SATISFIED` is defined and the run-time preconditions are not satisfied .]]
+
+]
+
+[endsect]
+[///////////////////////////////]
+[section:get2 `get(strict_lock<nested_strict_lock<Lock>>&)`]
+
+ template <class Lock>
+ T& get(nested_strict_lock<Lock>& lk);
+ template <class Lock>
+ const T& get(nested_strict_lock<Lock>& lk) const;
+
+[variablelist
+
+[[Requires:] [`is_same<mutex_type, typename Lock::mutex_type>` and the `lk` parameter must be locking the associated mutex.]]
+
+[[Returns:] [A reference to the cloaked object ]]
+
+[[Throws:] [__lock_error__ if `BOOST_THREAD_THROW_IF_PRECONDITION_NOT_SATISFIED` is defined and the run-time preconditions are not satisfied .]]
+
+]
+
+[endsect]
+
+[///////////////////////////////]
+[section:get3 `get(strict_lock<nested_strict_lock<Lock>>&)`]
+
+ template <class Lock>
+ T& get(Lock& lk);
+ template <class Lock>
+ T const& get(Lock& lk) const;
+
+[variablelist
+
+[[Requires:] [`Lock` is a model of __StrictLock, `is_same<mutex_type, typename Lock::mutex_type>` and the `lk` parameter must be locking the associated mutex.]]
+
+[[Returns:] [A reference to the cloaked object ]]
+
+[[Throws:] [__lock_error__ if `BOOST_THREAD_THROW_IF_PRECONDITION_NOT_SATISFIED` is defined and the run-time preconditions are not satisfied .]]
+
+]
+
+[endsect]
+
+[endsect]
+[///////////////////////////////]
+[section:swap `swap(externally_locked, externally_locked&)`]
+
+ template <typename T, typename MutexType>
+ void swap(externally_locked<T, MutexType> & lhs, externally_locked<T, MutexType> & rhs)
+
+[endsect]
+
+
+[endsect]
[section:shared_lock_guard Class template `shared_lock_guard`]
@@ -2178,7 +2492,7 @@
[endsect]
-[section:lock_range Non-member function `lock(begin,end)`]
+[section:lock_range Non-member function `lock(begin,end)` // EXTENSION]
template<typename ForwardIterator>
void lock(ForwardIterator begin,ForwardIterator end);
@@ -2247,7 +2561,7 @@
[endsect]
-[section:try_lock_range Non-member function `try_lock(begin,end)`]
+[section:try_lock_range Non-member function `try_lock(begin,end)` // EXTENSION]
template<typename ForwardIterator>
ForwardIterator try_lock(ForwardIterator begin,ForwardIterator end);
@@ -2280,6 +2594,84 @@
]
[endsect]
+[endsect]
+
+[section:lock_factories Lock Factories - EXTENSION]
+
+ namespace boost
+ {
+
+ template <typename Lockable>
+ unique_lock<Lockable> make_unique_lock(Lockable& mtx); // EXTENSION
+
+ template <typename Lockable>
+ unique_lock<Lockable> make_unique_lock(Lockable& mtx, adopt_lock_t); // EXTENSION
+ template <typename Lockable>
+ unique_lock<Lockable> make_unique_lock(Lockable& mtx, defer_lock_t); // EXTENSION
+ template <typename Lockable>
+ unique_lock<Lockable> make_unique_lock(Lockable& mtx, try_to_lock_t); // EXTENSION
+
+ #if ! defined(BOOST_NO_CXX11_HDR_TUPLE)
+ template <typename ...Lockable>
+ std::tuple<unique_lock<Lockable> ...> make_unique_locks(Lockable& ...mtx); // EXTENSION
+ #endif
+ }
+
+[section:make_unique_lock Non Member Function `make_unique_lock(Lockable&)`]
+
+ template <typename Lockable>
+ unique_lock<Lockable> make_unique_lock(Lockable& mtx); // EXTENSION
+
+
+[variablelist
+
+[[Returns:] [a __unique_lock as if initialized with `unique_lock<Lockable>(mtx)`.]]
+
+[[Throws:] [Any exception thrown by the call to `__unique_lock<Lockable>(mtx)`.]]
+
+]
+
+[endsect]
+
+[section:make_unique_lock_t Non Member Function `make_unique_lock(Lockable&,tag)`]
+
+ template <typename Lockable>
+ unique_lock<Lockable> make_unique_lock(Lockable& mtx, adopt_lock_t tag); // EXTENSION
+
+ template <typename Lockable>
+ unique_lock<Lockable> make_unique_lock(Lockable& mtx, defer_lock_t tag); // EXTENSION
+
+ template <typename Lockable>
+ unique_lock<Lockable> make_unique_lock(Lockable& mtx, try_to_lock_t tag); // EXTENSION
+
+
+[variablelist
+
+[[Returns:] [a __unique_lock as if initialized with `unique_lock<Lockable>(mtx, tag)`.]]
+
+[[Throws:] [Any exception thrown by the call to `__unique_lock<Lockable>(mtx, tag)`.]]
+
+]
+
+[endsect]
+
+
+[section:make_unique_locks Non Member Function `make_unique_locks(Lockable& ...)`]
+
+ template <typename ...Lockable>
+ std::tuple<unique_lock<Lockable> ...> make_unique_locks(Lockable& ...mtx); // EXTENSION
+
+[variablelist
+[[Effect:] [Locks all the mutexes.]]
+[[Returns:] [a std::tuple of unique __unique_lock owning each one of the mutex.]]
+
+[[Throws:] [Any exception thrown by `boost::lock(mtx...)`.]]
+
+]
+
+
+[endsect]
[endsect]
+
Modified: trunk/libs/thread/doc/shared_mutex_ref.qbk
==============================================================================
--- trunk/libs/thread/doc/shared_mutex_ref.qbk (original)
+++ trunk/libs/thread/doc/shared_mutex_ref.qbk 2012-12-16 17:40:10 EST (Sun, 16 Dec 2012)
@@ -5,7 +5,7 @@
http://www.boost.org/LICENSE_1_0.txt).
]
-[section:shared_mutex Class `shared_mutex`]
+[section:shared_mutex Class `shared_mutex` -- EXTENSION]
#include <boost/thread/shared_mutex.hpp>
@@ -62,7 +62,7 @@
[endsect]
-[section:upgrade_mutex Class `upgrade_mutex`]
+[section:upgrade_mutex Class `upgrade_mutex` -- EXTENSION]
#include <boost/thread/shared_mutex.hpp>
@@ -143,7 +143,7 @@
[endsect]
-[section:null_mutex Class `null_mutex`]
+[section:null_mutex Class `null_mutex` -- EXTENSION]
#include <boost/thread/null_mutex.hpp>
Modified: trunk/libs/thread/doc/sync_tutorial.qbk
==============================================================================
--- trunk/libs/thread/doc/sync_tutorial.qbk (original)
+++ trunk/libs/thread/doc/sync_tutorial.qbk 2012-12-16 17:40:10 EST (Sun, 16 Dec 2012)
@@ -11,10 +11,15 @@
[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2406.html Mutex, Lock, Condition Variable Rationale] adds rationale for the design decisions made for mutexes, locks and condition variables.
-[section:locks Locks]
In addition to the C++11 standard locks, Boost.Thread provides other locks and some utilities that help the user to make their code thread-safe.
+[include internal_locking.qbk]
+
+[include external_locking.qbk]
+
+[section:with Executing Around a Function]
+
In particular, the library provides some lock factories.
template <class Lockable, class Function>
@@ -34,6 +39,6 @@
});
-[endsect] [/ Locks]
+[endsect] [/ With]
[endsect] [/ Tutorial]
Modified: trunk/libs/thread/doc/thread.qbk
==============================================================================
--- trunk/libs/thread/doc/thread.qbk (original)
+++ trunk/libs/thread/doc/thread.qbk 2012-12-16 17:40:10 EST (Sun, 16 Dec 2012)
@@ -146,6 +146,7 @@
[def __boost_thread__ [*Boost.Thread]]
[def __not_a_thread__ ['Not-a-Thread]]
[def __interruption_points__ [link interruption_points ['interruption points]]]
+[def __lock_error__ `lock_error`]
[def __mutex__ [link thread.synchronization.mutex_types.mutex `boost::mutex`]]
[def __try_mutex__ [link thread.synchronization.mutex_types.try_mutex `boost::try_mutex`]]
@@ -170,7 +171,8 @@
[def __shared_lock_guard [link thread.synchronization.other_locks.shared_lock_guard `shared_lock_guard`]]
[def __shared_lock_guard_constructor_adopt [link thread.synchronization.other_locks.shared_lock_guard `shared_lock_guard`]]
-[def __strict_lock [link thread.synchronization.other_locks.strict_lock `strict_lock`]]
+[def __strict_lock [link thread.synchronization.other_locks.strict_locks.strict_lock `strict_lock`]]
+[def __nested_strict_lock [link thread.synchronization.other_locks.strict_locks.nested_strict_lock `nested_strict_lock`]]
[def __thread__ [link thread.thread_management.thread `boost::thread`]]
Boost-Commit list run by bdawes at acm.org, david.abrahams at rcn.com, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk