|
Boost-Commit : |
Subject: [Boost-commit] svn:boost r51267 - in sandbox/synchro/libs/synchro/doc: . reference
From: vicente.botet_at_[hidden]
Date: 2009-02-16 03:17:53
Author: viboes
Date: 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
New Revision: 51267
URL: http://svn.boost.org/trac/boost/changeset/51267
Log:
Boost.Synchro V0.0.0
More documentation
Text files modified:
sandbox/synchro/libs/synchro/doc/appendices.qbk | 4
sandbox/synchro/libs/synchro/doc/history.qbk | 38 +
sandbox/synchro/libs/synchro/doc/installation.qbk | 9
sandbox/synchro/libs/synchro/doc/introduction.qbk | 278 ++++++++-
sandbox/synchro/libs/synchro/doc/introduction_traits_and_concepts.qbk | 4
sandbox/synchro/libs/synchro/doc/overview.qbk | 55 +
sandbox/synchro/libs/synchro/doc/reference.qbk | 31
sandbox/synchro/libs/synchro/doc/reference/adaptive_lock.qbk | 18
sandbox/synchro/libs/synchro/doc/reference/condition_lockable.qbk | 66 +-
sandbox/synchro/libs/synchro/doc/reference/lockable_adapter.qbk | 38 +
sandbox/synchro/libs/synchro/doc/reference/lockable_concept.qbk | 25
sandbox/synchro/libs/synchro/doc/reference/locker_concepts.qbk | 102 ++
sandbox/synchro/libs/synchro/doc/reference/monitor.qbk | 141 ++--
sandbox/synchro/libs/synchro/doc/reference/null_mutex.qbk | 90 +-
sandbox/synchro/libs/synchro/doc/reference/poly_lock.qbk | 16
sandbox/synchro/libs/synchro/doc/reference/process_synchronization_family.qbk | 8
sandbox/synchro/libs/synchro/doc/reference/thread_synchronization_family.qbk | 13
sandbox/synchro/libs/synchro/doc/references.qbk | 42
sandbox/synchro/libs/synchro/doc/synchro.qbk | 1
sandbox/synchro/libs/synchro/doc/tutorial.qbk | 1142 ----------------------------------------
20 files changed, 651 insertions(+), 1470 deletions(-)
Modified: sandbox/synchro/libs/synchro/doc/appendices.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/appendices.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/appendices.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -25,6 +25,10 @@
[heading Tasks to do before review]
+* `array_locker` and `tuple_locker` locker ocntainers.
+* A complement to the already present in Boost.Thread `lock`, `try_lock` free functions:
+ * `lock_until`, `lock_for`, `try_lock_until`, `try_lock_for`
+
[heading For later releases]
[endsect]
Modified: sandbox/synchro/libs/synchro/doc/history.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/history.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/history.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -7,12 +7,36 @@
[section:history Appendix A: History]
-[variablelist
-[
- [Mars 23, 2008]
- [Synchro is born.
- ]
-]
-]
+[section [*Version 0.1.0, Febraury 16, 2009] ['Announcement of Synchro]]
+[*Features:]
+
+* A uniform usage of Boost.Thread and Boost.Interprocess synchronization mechanisms based on lockables(mutexes) concepts and locker(guards) concepts.
+
+ * lockables traits and lock generators,
+ * lock adapters of the Boost.Thread and Boost.Interprocess lockable models,
+ * locker adapters of the Boost.Thread and Boost.Interprocess lockers models,
+ * complete them with the corresponding models for single-threaded programms: `null_mutex` and `null_condition` classes,
+ * locking families,
+ * `semaphore`,
+ * `condition_lockable` lock put toghether a lock and its associated conditions.
+
+* A coherent way exception based timed lock functions and constructor,
+
+* A rich palete of lockers as
+
+ * `strict_locker`, `nested_strict_locker`,
+ * `condition_locker`,
+ * `reverse_locker`, `nested_reverse_locker`,
+ * `locking_ptr`, `on_derreference_locking_ptr`,
+ * `externally_locked`,
+
+* A polymorphic lockable hierarchy.
+
+* High-level abstractions for handling more complicated synchronization problems, including
+ * `monitor` for guaranteeing exclusive access to an object, and
+ * a so-called rendezvous mechanism for handling direct communication between objects `concurrent_components` via `ports` using an accept-synchronize protocol based on the design of the concurrency library in the Beta language.
+
+
+[endsect]
[endsect]
Modified: sandbox/synchro/libs/synchro/doc/installation.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/installation.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/installation.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -31,8 +31,9 @@
[heading Requirements]
[/=========================]
-[*Boost.Synchro] depends on Boost. You must use either Boost version 1.35.x
-or the version in SVN trunk. In particular, [*Boost.Synchro] depends on:
+[*Boost.Synchro] depends on Boost. You must use either Boost version 1.38.x
+or the version in SVN trunk (even if Boost version 1.35.x should works also).
+In particular, [*Boost.Synchro] depends on:
[variablelist
@@ -62,7 +63,7 @@
[heading Thread safety]
[/====================]
-All functions in the library are exception-safe except:
+All functions in the library are thread-unsafe except when noted explicitly.
[/=======================]
@@ -72,7 +73,7 @@
* GCC 3.4.4 Cygwin
* GCC 3.4.6 Linux
-* GCC 4.3.2 Cygwin
+[/* GCC 4.3.2 Cygwin]
* GCC 4.1.2 Linux
[note Please send any questions, comments and bug reports to boost <at> lists <dot> boost <dot> org.]
Modified: sandbox/synchro/libs/synchro/doc/introduction.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/introduction.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/introduction.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -12,11 +12,51 @@
[section Mapping the current mutexes (Boost.Thread and Boost/Interprocess) to the common concepts]
+The mapping from the current mutexes and scoped guards (Boost.Thread and Boost/Interprocess) to common concepts has been done by adapting the required signatures to the underlying models. For example thread_timed_mutex is defied as:
+
+ class thread_timed_mutex
+ : public lock_traits_base<
+ multi_threaded_tag,
+ exclusive_lock_tag,
+ non_recursive_tag,
+ has_timed_interface_tag,
+ process_lifetime_tag,
+ anonymous_tag,
+ timed_mutex
+ > {
+ public:
+ typedef boost::condition_variable_any best_condition_type;
+ typedef boost::condition_variable_any best_condition_any_type;
+
+ //Non-copyable
+ BOOST_COPY_CONSTRUCTOR_DELETE(thread_mutex) /*< disable copy construction >*/
+ BOOST_COPY_ASSIGNEMENT_DELETE(thread_mutex) /*< disable copy asignement >*/
+ thread_timed_mutex () {}
+
+ bool try_lock_until(system_time const & abs_time) {
+ return timed_lock(abs_time);
+ }
+ template<typename TimeDuration>
+ bool try_lock_for(TimeDuration const & relative_time) {
+ return timed_lock(relative_time);
+ }
+
+ void lock_until(system_time const & abs_time) {
+ if(!timed_lock(abs_time)) throw timeout_exception();
+ }
+ template<typename TimeDuration>
+ void lock_for(TimeDuration const & relative_time) {
+ if(!timed_lock(relative_time)) throw timeout_exception();
+ }
+ };
+
+Note that only the functions for which the interface change are defined. The others are inherited.
+
[endsect]
[section Condition lockable]
-The library provides condition lockable, which allows a condition variable to be associated with a Lockable.
+Based on the idead of Kevlin Henney, the library provides condition lockable, which allows a condition variable to be associated with a Lockable.
Treating condition locking as a property of Lockable rather than viceversa has the benefit of making clear how something is locked and accessed, as it were emphasising it in the first person.
@@ -24,12 +64,12 @@
public:
...
product *pull() {
- guard.lock();
+ mtx.lock();
while(queue.empty())
guard.relock_on(not_empty);
product *pulled = queue.front();
queue.pop();
- guard.unlock();
+ mtx.unlock();
return pulled;
}
...
@@ -41,10 +81,10 @@
public:
...
product *pull() {
- guard.lock_when(not_empty, has_products(queue));
+ mtx.lock_when(not_empty, has_products(queue));
product *pulled = queue.front();
queue.pop();
- guard.unlock();
+ mtx.unlock();
return pulled;
}
...
@@ -52,12 +92,13 @@
[endsect]
-[section Timeouts]
+[section Exception-based Timed Locks]
-The library supports timeout exception for all the locking functions having a time or duration parameter.
-* A lock with a timeout parameter throws a timed_out exception on expiry
-* A try_lock with a timeout simply returns false on expiry
-* Any of the conditional locks throw a timed_out exception on expiry
+Based on the idead of Kevlin Henney, the library supports timeout exception for all the locking functions having a time or duration parameter.
+* A lock with a timeout i.e. a time or a duration, parameter throws a `timeout_exception` on expiry
+* A `try_lock` with a timeout simply returns false on expiry
+* Any of the conditional locks throw a `timeout_exception` on expiry
+* all the locker constructors with the first parameter a timeout.
Use of timeouts can create more robust programs, by not blocking forever, but at the same time one needs to avoid annoyingly arbitrary limits.
@@ -68,7 +109,7 @@
Typically, object-oriented programs use object-level locking by associating a synchronization object (mutex) with each object that is susceptible to be shared between threads. Then, code that manipulates the state of the object can synchronize by locking that object. Inside a synchronized section, the mutex associated with the object is locked, and consequently that object's fields can be accessed safely.
-In C++, this fundamental idiom is typically implemented with a helper Locker object.
+In C++, this fundamental idiom is typically implemented with a helper Locker object or lock guard.
A locker is any object or function responsible for coordinating the use of lockable objects.
@@ -93,13 +134,9 @@
boost::synchro::unique_locker<Lockable> scoped(guard);
- boost::synchro::unique_deferred_locker<Lockable> scoped(guard);
- boost::synchro::unique_adopt_locker<Lockable> scoped(guard);
- boost::synchro::unique_try_locker<Lockable> scoped(guard);
-
[*Strict lockers]
-A strict locker is a scoped lock guard ensuring the mutex is locked on the scope of the lock, by locking the mutex on construction and unlocking it on destruction.
+Strict lockers were first introduced by Andrei Alexandrescu. A strict locker is a scoped lock guard ensuring the mutex is locked on the scope of the lock, by locking the mutex on construction and unlocking it on destruction.
`boost::lock_guard` could be seen as a strict_locker if the following constructor didn't exists
@@ -107,16 +144,28 @@
We can say that lock_guard is a strict locker "sur parolle".
-
There is a const function that is very useful when working with strict lockers and external locking which check is the strict locker is locking an instace of a lockable.
bool is_locking(lockable_type* l) const;
The library provides three strict lockers
-* `strict_locker`: is the basic strict locker
+* `strict_locker`: is the basic strict locker, special use when doing external locking.
* `neested_strict_locker`: is a strict_locker of another locker as a unique_lock.
-* `conditional_locker` : is a strict locker with the condition_lockable interface
+* `conditional_unique_locker` and `conditional_shared_locker` : are strict lockers with the condition_lockable interface. These are the synchronizer of the monitor class.
+
+ class product_queue {
+ public:
+ ...
+ product *pull() {
+ conditional_unique_locker<> _(mtx, not_empty, has_products(queue));
+ product *pulled = queue.front();
+ queue.pop();
+ mtx.unlock();
+ return pulled;
+ }
+ ...
+ };
and a meta function `is_strict_locker` which states if a locker is a strict locker
@@ -124,53 +173,148 @@
[*Try lockers]
-Most of the lockers defined in Boost.Thread and Boost.Interprocess are TryLockers, i.e. them allows to be initialize is a such way that instead of locking on the constructor with lock() they can try to lock with try_lock().
+A Try Locker is a Locker that initialize it in a such way that instead of locking on the constructor with lock() they can try to lock with try_lock(). Most of the lockers defined in Boost.Thread and Boost.Interprocess could be cosidered as TryLockers, i.e. them initialize in this way when boost::try_to_lock is given as parameter.
The following code shows one way to use the TryLocker:
product *product_queue::try_pull() {
product *pulled = 0;
- boost::unique_lock<boost::mutex> scoped(guard, boost::try_to_lock);
- if(scoped && !queue.empty()) {
+ boost::unique_lock<boost::mutex> locker(mtx, boost::try_to_lock);
+ if(locker && !queue.empty()) {
pulled = queue.front();
queue.pop();
}
return pulled;
}
-If we use interprocess mutexes the following line
+All of them use a safe strategy for a Boolean conversion which use a member pointer rather than a bool, which is typically too permissive:
+
+ typedef bool try_locker::*is_locked;
+ operator is_locked() const {
+ return locked ? &try_locker::locked : 0;
+ }
+
+If we use interprocess mutexes we nee to replace the following line
boost::unique_lock<boost::mutex> scoped(guard, boost::try_to_lock);
-need to be changed by
+by
boost::interprocess::scoped_lock<boost::interprocess::interprocess_mutex> scoped(guard, boost::interprocess::try_to_lock);
-All of them use a safe strategy for a Boolean conversion which use a member pointer rather than a bool, which is typically too permissive:
+There are other TryLockers in Boost.Thread defined as a member typedef scoped_try_lock. The semantics of each constructor and member function are identical to those of boost::unique_lock<Lockable> for the same Lockable, except that the constructor that takes a single reference to a mutex will call m.try_lock() rather than m.lock()
- typedef bool try_locker::*is_locked;
- operator is_locked() const {
- return locked ? &try_locker::locked : 0;
+ boost::mutex::scoped_try_lock locker(mtx);
+
+The library defines in a generic way a try_unique_locker adapter which takes care of naming differences and that can be used like
+
+ boost::synchro::unique_try_locker<Lockable> locker(mtx);
+
+for any model of Lockable.
+
+[*Exception-based Timed Lockers]
+
+In addition to supporting timeout exception for Lock, the library supports them also for Lockers on the same locking functions as for Locks but also for all the locker constructors.
+
+The use of the parameter throw_lock_t could be cumberscome for applications expecting a exception-based timed locks rather than checking if the lock is owned by the locker. For this use case the library provides a special kind of Lokers, ExceptionBaseTimedLockers. The semantics of each constructor and member function are identical to those of boost::unique_locker<Lockable> for the same Lockable, except that the constructor that takes a time or a duration as first parameter in a addition to the reference to a mutex will call m.lock_until(t) or m.lock_for(d) rather than m.try_lock_until(t) or m.try_lock_for(d) and so a timeout_exception is possible on the constructor.
+
+When the application needs to lock several locks at the same time the natural and exception safe is
+
+ while (polling) {
+ t=now()+100;
+ boost::unique_lock<boost::mutex> l1(m1, t);
+ boost::unique_lock<boost::mutex> l2(m2, t);
+ boost::unique_lock<boost::mutex> l3(m3, t);
+ if (l1.has_lock() && l2.has_lock() && l3.has_lock() {
+ foo();
+ polling = false;
+ } else execute_on_failed();
}
-Two mechanisms can allow a try_locker to be used directly in a condition: a variable can be declared in a condition if its type is convertible to bool and temporaries are scope bound to references to const. The common usage will be captured and the mechanism further generalised for convenience, but the model and code above demonstrates the essential concepts.
+The single problem with this code is that we will lock `m2` even if `l1` do not owns the lock `m1`. When we try to optimize this
- typedef const a_try_locker<mutex> &locked;
- product *product_queue::try_pull() {
- product *pulled = 0;
- if(locked scoped = guard) {
- if(!queue.empty()) {
- pulled = queue.front();
- queue.pop();
- }
- }
- return pulled;
+ while (polling) {
+ t=now()+100;
+ boost::unique_lock<boost::mutex> l1(m1, t);
+ if (l1.has_lock() {
+ boost::unique_lock<boost::mutex> l2(m2, t);
+ if (l2.has_lock() {
+ boost::unique_lock<boost::mutex> l3(m3, t);
+ if (l2.has_lock() {
+ foo();
+ polling = false;
+ } else execute_on_failed();
+ } else execute_on_failed();
+ } else execute_on_failed();
+ }
+
+We found that this starts to be unmaintenable. What is event wrost is that as the preceding one is subject to deadlock if another thread acquire the locks in a different order.
+
+[*try_lock_until and try_lock_for free functions]
+
+To avoid this we can request the acquisition of all the locks toghether (letting the function to try any order), as it does the function try_lock of Boost.Threads, but adding this time a expiration period.
+
+ while (polling) {
+ if (boost::try_lock_for(100, m1, m2, m3)) {
+ foo();
+ polling = false;
+ } else execute_on_failed();
+ }
+
+While this soleve the deadlock problem, this code is that it is not exception safe. With exception based lockers we can do the following
+(note that the time is given as first aregument to the locker constructor)
+
+ while (polling)
+ try {
+ t=now()+100;
+ boost::unique_locker<boost::mutex> l1(t, m1);
+ boost::unique_locker<boost::mutex> l2(t, m2);
+ boost::unique_locker<boost::mutex> l3(t, m3);
+ foo();
+ polling = false;
+ } catch (timeout_exception& ex) {execute_on_failed(); }
+
+[*`locker_tuples` or `locker_array` of Locker containers]
+
+While this code is exception safe and do not locks m2 if m1 id not acquire is subject to deadlock.
+We can go a step ahead and mix the advantage of taking all the locks at once and making the acquisition block scoped. In order to do that we need either a array_locker or a tuple_locker depending on wether the locks are homogeneus or not. The library provides both of them. These locker containers follows the same rules as the element wise lockers. If the time comes after the locks no exception is thrown on timeout and if given as the first parameter a exception will be thown when the time will expire.
+
+So the preceding code becomes without timeout exceptions
+
+ while (polling) {
+ boost::synchro::array_locker<boost::mutex, 3> lk(m1, m2, m3, 100);
+ if (lk.owns_lock()) {
+ foo();
+ polling = false;
+ } else execute_on_failed();
}
+or with timeout exception
-The library defines a try_locker adapter which take care of naming differences and that can be used like
+ while (polling)
+ try { boost::synchro::array_locker<boost::mutex, 3> lk(100, m1, m2, m3);
+ foo();
+ polling = false;
+ } catch (boost::synchro::timed_exception& ex) { execute_on_failed(); }
- boost::synchro::unique_try_locker<Lockable> scoped(guard);
+When the Locks locked by an `array_locker` are not homogeneus we need some kind of tuple.
+ while (polling)
+ try { boost::synchro::tuple_locker<T1, T2, T1> lk(100, m1, m2, m3);
+ foo();
+ polling = false;
+ } catch (boost::synchro::timed_exception& ex) { execute_on_failed(); }
+
+
+[*`lock_until` and `lock_for` free functions]
+
+For completude the exception based timed multi lock functions `lock_until` and `lock_for` are also provided.
+
+ while (polling)
+ try {
+ boost::lock_for(100, m1, m2, m3);
+ foo();
+ polling = false;
+ } catch (timeout_exception& ex) {execute_on_failed(); }
[*External lockers]
@@ -195,6 +339,58 @@
[section:poly Polymorphic lockable]
+The locks classes introduced previously are a non-polymorphic classes. Clearly, many of the synchronisation primitives support common operations, and hence interfaces. In some cases a more general interface is useful.
+
+The synchronised interface class may be used explicitly as a base class for a class supporting synchronisation.
+
+ struct exclusive_lock {
+ virtual ~exclusive_lock()=0;
+ virtual void lock()=0;
+ virtual void unlock()=0;
+ virtual bool try_lock()=0;
+ };
+
+More usefully for primitives, which are best left as non-polymorphic, an adaptor class is used to provide the interface -- run-time polymorphism -- on behalf of anything supporting the correctly named functions - compile time polymorphism. It easier to take a nonpolymorphic class and adapt it to be polymorphic, than it is do it the other way around: the overhead and semantics of polymorphism can only be introduced to a class, not removed.
+
+template <typename Lockable>
+class exclusive_lock_adapter : public virtual exclusive_lock
+{
+ exclusive_lock_adapter(): lock_() {}
+ virtual ~exclusive_lock_adapter() {}
+
+ virtual void lock() {lock_.lock();}
+ virtual void unlock() {lock_.unlock();}
+ virtual bool try_lock() { return lock_.try_lock();}
+protected:
+ Lockable lock_;
+};
+
+
+
+[endsect]
+
+[section Synchronized Block - language-like]
+
+
+mutual exclusion with automatic objects.
+
+ {
+ scoped_guard<boost_mutex> lock(l);
+ return foo(); // lock released
+ }
+
+language-like mutual exclusion.
+
+ synchronized(l) return foo(); // lock released
+
+ #define BOOST_SYNCHRONIZED_VAR(VARS_DECLARATION) if (bool stop_ = false) {} else \
+ for (VARS_DECLARATION; !stop_; stop_ = true)
+
+ #define BOOST_SYNCHRONIZED(MUTEX) BOOST_SYNCHRONIZED_VAR(boost::scoped_guard<boost::mutex> __lock(MUTEX))
+
+With this macro the user can define
+ #define synchronized(MUTEX) BOOST_SYNCHRONIZED(MUTEX)
+
[endsect]
[section:conc Concurrent components]
Modified: sandbox/synchro/libs/synchro/doc/introduction_traits_and_concepts.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/introduction_traits_and_concepts.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/introduction_traits_and_concepts.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -6,11 +6,11 @@
/]
-[section:uniform Using thread, Interprocess and null synchronization mechanisms uniformly]
+[section:uniform Using Thread, Interprocess and Null synchronization mechanisms uniformly]
One of the problems when doing multi threaded application with Boost.Thread and Boost.Interprocess is that the synchronization mechanism of these two libraries even if they are very close since the release 1.35, there are some minor differences that make quite difficult to design a class that can work independently with synchronization mechanisms of both libraries.
-This library proposes some classes that allows to write code that can be used indistinguishably with thread or interprocess synchronization mechanisms. It is inspired on the work from [*C++ Threading - A Generic-Programming Approach] - Kevlin Henney and [*An OO Encapsulation of Lightweight OS Concurrency Mechanisms in the ACE Toolkit] Douglas C. Schmidt.
+This library proposes some classes that allows to write code that can be used indistinguishably with thread or interprocess synchronization mechanisms. This section is inspired on the work from [*C++ Threading - A Generic-Programming Approach] - Kevlin Henney.
[*Lock substitutability]
Modified: sandbox/synchro/libs/synchro/doc/overview.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/overview.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/overview.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -15,33 +15,50 @@
To date, C++ multi threaded programs that need to be efficient use the same mutexes, semaphores, and events that Dijkstra described 40 years ago. This unfortunate state of affairs makes multi threaded programs difficult to design, debug, ensure correct, optimize, maintain, and analyze formally. Consequently, building tools that automate detection of race conditions and deadlocks is highly desirable.
-This can not be done if the basic lock and lockers take a different form depending of the library provider. We need to states the bases through concepts. Specific mapping to these concepts is needed for the current Boost locks and lockers. This mapping can be done by the owner of the candidate model or using some explicit adapters.
+I believe that most of the synchronization mechanisms applied to multi-threaded programs works as well for multi-process programs. This can not be done if the basic facilities mutexes and condition variables are not based on common concepts and take a different form depending of the library provider. We need to states these bases through concepts.
+
+The main sources of inspiration of this library were
+
+* the papers of Kevlin Henney about asynchronous C++ [@http://www.two-sdg.demon.co.uk/curbralan/papers/accu/MoreC++Threading.pdf More C++ Threading - From Procedural to Generic, by Example] and its C++0x proposal [[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1883.pdf N1833 - Preliminary Threading Library Proposal for TR2]].
+* the papers of Andrei Alexandrescu on multi-threading programming [@http://www.ddj.com/cpp/184403766 volatile - Multithreaded Programmer's Best Friend]
+* the ACE framework of Douglas C. Schmidt [@http://www.cs.wustl.edu/~schmidt/PDF/ACE-concurrency.pdf An OO Encapsulation of Lightweight OS Concurrency Mechanisms in the ACE Toolkit].
+
+This library is a compilation of what I have found in the literature not yet present in Boost. My main concen has been to boostify all these ideas in a coherent way.
[*Boost.Synchro] provides:
-* A uniform usage of Boost.Thread and Boost.Interprocess synchronization mechanisms based on some lock(mutexes) concepts, lock traits, locker(guards) concepts and locking families.
- * add lock adapters to the Boost.Thread and Boost.Interprocess lockable models
- * add locker adapters to the Boost.Thread and Boost.Interprocess lockers models
- * `semaphore`, `null_mutex` and `null_condition` classes
- * add lock functions throwing a timeout exception when the the operation is not achieved before a given time is reached or a duration elapsed.
- * add a condition_lockable lock
-
-* Some additional lockers are also provided as
- * `strict_locker`,
- * `nested_strict_locker`,
+* A uniform usage of Boost.Thread and Boost.Interprocess synchronization mechanisms based on lockables(mutexes) concepts and locker(guards) concepts.
+
+ * lockables traits and lock generators,
+ * lock adapters of the Boost.Thread and Boost.Interprocess lockable models,
+ * locker adapters of the Boost.Thread and Boost.Interprocess lockers models,
+ * complete them with the corresponding models for single-threaded programms: `null_mutex` and `null_condition` classes,
+ * locking families,
+ * `semaphore`,
+ * `condition_lockable` lock which put toghether a lock and its associated conditions.
+
+* A coherent way exception based timed lock approach for functions and constructors,
+
+* A rich palete of lockers as
+
+ * `strict_locker`, `nested_strict_locker`,
* `condition_locker`,
- * `locking_ptr`,
+ * `reverse_locker`, `nested_reverse_locker`,
+ * `locking_ptr`, `on_derreference_locking_ptr`,
* `externally_locked`,
- * `reverse_locker` and
- * `nested_reverse_locker`.
+
+* `array_locker` and `tuple_locker` locker ocntainers.
-* Introduce high-level abstractions for handling more complicated synchronization problems, including
- * `monitor` for guaranteeing exclusive access to an object, and
- * a so-called rendezvous mechanism for handling direct communication between objects `concurrent_components` via `ports` using an accept-synchronize protocol.
+* A complement to the already present in Boost.Thread `lock`, `try_lock` free functions:
+
+ * `lock_until`, `lock_for`, `try_lock_until`, `try_lock_for`
-* In addition a polymorph lockable hierarchy is also included.
+* A polymorphic lockable hierarchy.
+
+* High-level abstractions for handling more complicated synchronization problems, including
+ * `monitor` for guaranteeing exclusive access to an object, and
+ * a so-called rendezvous mechanism for handling direct communication between objects `concurrent_components` via `ports` using an accept-synchronize protocol based on the design of the concurrency library in the Beta language.
-This library is much more a compilation of what I have found in the literature that is not yet present in Boost.
[/====================================]
[heading How to Use This Documentation]
Modified: sandbox/synchro/libs/synchro/doc/reference.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/reference.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/reference.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -10,8 +10,8 @@
[section Lockables]
[/include concepts/Lockable.qbk]
[include reference/lockable_traits.qbk]
-[include reference/lock_generator.qbk]
[include reference/lockable_concept.qbk]
+[include reference/lock_generator.qbk]
[include reference/lockable_adapter.qbk]
[endsect]
@@ -22,6 +22,19 @@
[include reference/condition_lockable.qbk]
[endsect]
+[section Lockers]
+[/include concepts/Locker.qbk]
+[include reference/locker_concepts.qbk]
+[/include reference/lockers.qbk]
+[include reference/is_strict_locker.qbk]
+[include reference/strict_locker.qbk]
+[include reference/reverse_locker.qbk]
+[include reference/nested_reverse_locker.qbk]
+[include reference/condition_locker.qbk]
+[include reference/externally_locked.qbk]
+[include reference/locking_ptr.qbk]
+[include reference/on_derreference_locking_ptr.qbk]
+[endsect]
[section Single-threaded]
[include reference/null_mutex.qbk]
@@ -54,23 +67,9 @@
[section Polymorphic Locks]
[include reference/poly_lock.qbk]
[include reference/lock_adapter.qbk]
-[include reference/adaptive_lock.qbk]
+[/include reference/adaptive_lock.qbk]
[endsect]
-[section Lockers]
-[/include concepts/Locker.qbk]
-[include reference/locker_concepts.qbk]
-[include reference/is_strict_locker.qbk]
-[include reference/strict_locker.qbk]
-[include reference/reverse_locker.qbk]
-[include reference/nested_reverse_locker.qbk]
-[include reference/condition_locker.qbk]
-[include reference/externally_locked.qbk]
-[include reference/locking_ptr.qbk]
-[include reference/on_derreference_locking_ptr.qbk]
-[endsect]
-
-
[section Other]
[include reference/semaphore.qbk]
[endsect]
Modified: sandbox/synchro/libs/synchro/doc/reference/adaptive_lock.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/reference/adaptive_lock.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/reference/adaptive_lock.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -11,24 +11,6 @@
[section:poly_adaptive_lock_hpp Header `<boost/synchro/poly/adaptive_lock.hpp>`]
[/==========================================================================================]
-[section Class `lock_adapter`]
-[*Synopsis]
-
-[*Description]
-
-More usefully for primitives, which are best left as
-non-polymorphic, an adaptor class can be used to provide the interface - run-time
-polymorphism - on behalf of anything supporting the correctly named functions -
-sometimes known as compile time polymorphism. It easier to take a nonpolymorphic
-class and adapt it to be polymorphic, than it is do it the other way
-around: the overhead and semantics of polymorphism can only introduced to a class,
-not removed.
-
-[*`lock_adapter` public member functions]
-
-# `;`
-
-[endsect]
[section Class `adaptive_lock`]
An adaptive general locking class that defers the decision of lockable type to run time.
Modified: sandbox/synchro/libs/synchro/doc/reference/condition_lockable.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/reference/condition_lockable.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/reference/condition_lockable.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -25,39 +25,39 @@
Requiring the user of a condition variable to implement a while loop to verify a condition's predicate is potentially error prone. It can be better encapsulated by passing the predicate as a function object to the locking function.
-template <
- typename Lockable=thread_mutex,
- class Condition=condition_safe<typename best_condition<Lockable>::type >
->
-class condition_lockable {
- : public Lockable {
- BOOST_CONCEPT_ASSERT((LockableConcept<Lockable>));
-public:
- typedef Lockable lockable_type;
- typedef Condition condition;
-
- condition_lockable();
- ~condition_lockable();
-
- void relock_on(condition & cond);
- void relock_on_until(condition & cond, boost::system_time 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>
- void relock_when_until(condition &cond, Predicate pred,
- boost::system_time const& abs_time);
- template<typename Predicate, typename duration_type>
- void relock_when_for(condition &cond, Predicate pred,
- duration_type const& rel_time);
-
-private:
- friend class boost::condition_variable;
- friend class boost::condition_variable_any;
- friend class boost::interprocess::condition;
-};
+ template <
+ typename Lockable=thread_mutex,
+ class Condition=condition_safe<typename best_condition<Lockable>::type >
+ >
+ class condition_lockable {
+ : public Lockable {
+ BOOST_CONCEPT_ASSERT((LockableConcept<Lockable>));
+ public:
+ typedef Lockable lockable_type;
+ typedef Condition condition;
+
+ condition_lockable();
+ ~condition_lockable();
+
+ void relock_on(condition & cond);
+ void relock_on_until(condition & cond, boost::system_time 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>
+ void relock_when_until(condition &cond, Predicate pred,
+ boost::system_time const& abs_time);
+ template<typename Predicate, typename duration_type>
+ void relock_when_for(condition &cond, Predicate pred,
+ duration_type const& rel_time);
+
+ private:
+ friend class boost::condition_variable;
+ friend class boost::condition_variable_any;
+ friend class boost::interprocess::condition;
+ };
[endsect]
Modified: sandbox/synchro/libs/synchro/doc/reference/lockable_adapter.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/reference/lockable_adapter.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/reference/lockable_adapter.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -24,8 +24,11 @@
[section Template Class `exclusive_lockable_adapter<>`]
template <typename Lockable>
- class exclusive_lockable_adapter : private boost::noncopyable {
+ class exclusive_lockable_adapter {
public:
+ BOOST_COPY_CONSTRUCTOR_DELETE(exclusive_lockable_adapter)
+ BOOST_COPY_ASSIGNEMENT_DELETE(exclusive_lockable_adapter)
+
typedef Lockable lockable_type;
typedef typename scope_tag<Lockable>::type scope;
typedef typename category_tag<Lockable>::type category;
@@ -34,7 +37,7 @@
typedef typename lifetime_tag<Lockable>::type lifetime;
typedef typename naming_tag<Lockable>::type naming;
-
+ exclusive_lockable_adapter() {}
void lock();
void unlock();
bool try_lock();
@@ -46,9 +49,15 @@
template <typename TimedLock>
class timed_lockable_adapter : public exclusive_lockable_adapter<TimedLock> {
public:
- bool timed_lock(system_time const & abs_time);
+ timed_lockable_adapter() {}
+
+ bool try_lock_until(system_time const & abs_time);
template<typename TimeDuration>
- bool timed_lock(TimeDuration const & relative_time);
+ bool try_lock_for(TimeDuration const & relative_time);
+
+ void lock_until(system_time const & abs_time);
+ template<typename TimeDuration>
+ void lock_for(TimeDuration const & relative_time);
};
[endsect]
@@ -57,10 +66,18 @@
template <typename SharableLock>
class shared_lockable_adapter : public timed_lockable_adapter<SharableLock> {
public:
+ shared_lockable_adapter() {}
void lock_shared();
bool try_lock_shared();
void unlock_shared();
- bool timed_lock_shared(system_time const& t);
+
+ bool try_lock_shared_until(system_time const& t);
+ template<typename TimeDuration>
+ bool try_lock_shared_for(TimeDuration const& t);
+
+ template<typename TimeDuration>
+ void lock_shared_for(TimeDuration const& t);
+ void lock_shared_until(system_time const& t);
};
@@ -70,14 +87,23 @@
template <typename UpgradableLock>
class upgrade_lockable_adapter : public shared_lockable_adapter<UpgradableLock>{
public:
+ upgrade_lockable_adapter();
+
void lock_upgrade();
+ bool try_lock_upgrade();
void unlock_upgrade();
void unlock_upgrade_and_lock();
void unlock_and_lock_upgrade();
void unlock_and_lock_shared();
void unlock_upgrade_and_lock_shared();
- bool timed_lock_upgrade(system_time const&t);
+
+ bool try_lock_upgrade_until(system_time const&t);
+ template<typename TimeDuration>
+ bool try_lock_upgrade_for(TimeDuration const&t);
+ void lock_upgrade_until(system_time const&t);
+ template<typename TimeDuration>
+ void lock_upgrade_for(TimeDuration const&t);
};
Modified: sandbox/synchro/libs/synchro/doc/reference/lockable_concept.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/reference/lockable_concept.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/reference/lockable_concept.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -39,7 +39,7 @@
l.unlock();
l.try_lock();
}
- Lockable l;
+ Lockable& l;
};
[endsect]
@@ -52,10 +52,12 @@
BOOST_CONCEPT_ASSERT((LockableConcept<Lockable>));
BOOST_CONCEPT_USAGE(TimedLockableConcept) {
- l.timed_lock(t);
- // l.timed_lock(1);
+ l.lock_until(t);
+ l.lock_for(boost::posix_time::seconds(1));
+ l.try_lock_until(t);
+ l.try_lock_for(boost::posix_time::seconds(1));
}
- Lockable l;
+ Lockable& l;
system_time t;
};
@@ -70,11 +72,14 @@
BOOST_CONCEPT_USAGE(ShareLockableConcept) {
l.lock_shared();
- l.timed_lock_shared(t);
+ l.lock_shared_until(t);
+ l.lock_shared_for(boost::posix_time::seconds(1));
l.try_lock_shared();
+ l.try_lock_shared_until(t);
+ l.try_lock_shared_for(boost::posix_time::seconds(1));
l.unlock_shared();
}
- Lockable l;
+ Lockable& l;
system_time t;
};
@@ -91,13 +96,17 @@
BOOST_CONCEPT_USAGE(UpgradeLockableConcept) {
l.lock_upgrade();
- l.timed_lock_upgrade(t);
+ l.lock_upgrade_until(t);
+ l.lock_upgrade_for(boost::posix_time::seconds(1));
+ l.try_lock_upgrade();
+ l.try_lock_upgrade_until(t);
+ l.try_lock_upgrade_for(boost::posix_time::seconds(1));
l.unlock_upgrade_and_lock();
l.unlock_and_lock_upgrade();
l.unlock_and_lock_shared();
l.unlock_upgrade_and_lock_shared();
}
- Lockable l;
+ Lockable& l;
system_time t;
};
Modified: sandbox/synchro/libs/synchro/doc/reference/locker_concepts.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/reference/locker_concepts.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/reference/locker_concepts.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -13,41 +13,109 @@
namespace boost { namespace synchro {
+ template <typename Locker> struct BasicLockerConcept;
+ template <typename Locker> struct LockerConcept;
+ template <typename Locker> struct TimedLockerConcept;
+ template <typename Locker> struct UniqueLockerConcept;
+ template <typename Locker> struct SharedLockerConcept;
+ template <typename Locker> struct UpgradeLockerConcept;
+ template <typename Locker> struct MovableLockerConcept;
+
+ }}
+
+[section Template Class `BasicLockerConcept<>`]
+
+ template <typename Locker>
+ struct BasicLockerConcept {
+ typedef typename lockable_type<Locker>::type lockable_type;
+
+ BOOST_CONCEPT_USAGE(BasicLockerConcept) {
+ const Locker l1(mtx_);
+ if (l1.is_locking(mtx_)) return;
+ if (l1.owns_lock()) return;
+ if (l1) return;
+ if (!l1) return;
+ }
+ lockable_type mtx_;
+ system_time t;
+ };
+
+[endsect]
+[section Template Class `LockerConcept<>`]
+
template <typename Locker>
struct LockerConcept {
+ BOOST_CONCEPT_ASSERT((BasicLockerConcept<Locker>));
typedef typename lockable_type<Locker>::type lockable_type;
+
BOOST_CONCEPT_USAGE(LockerConcept) {
- Locker l(mtx_);
+ Locker l2(mtx_, defer_lock);
+ Locker l3(mtx_, adopt_lock);
+ Locker l4(mtx_, try_to_lock);
+ l2.lock();
+ if (l2.try_lock()) return;
+ l2.unlock();
+ l2.release();
}
lockable_type mtx_;
+ system_time t;
};
+[endsect]
+[section Template Class `TimedLockerConcept<>`]
+
template <typename Locker>
- struct MovableLockerConcept {
+ struct TimedLockerConcept {
+ BOOST_CONCEPT_ASSERT((LockerConcept<Locker>));
typedef typename lockable_type<Locker>::type lockable_type;
- BOOST_CONCEPT_ASSERT((LockerConcept<lockable_type>));
- BOOST_CONCEPT_USAGE(MovableLockerConcept) {
- Locker l1(mtx_);
- Locker& l2(l1);
- Locker l3(l1.move());
- BOOST_ASSERT((l2.mutex()!=&mtx_));
- l3.lock();
- l2 = l3.move();
+ BOOST_CONCEPT_USAGE(TimedLockerConcept) {
+ const Locker l1(mtx_);
+ Locker l5(mtx_, t);
+ Locker l6(mtx_, boost::posix_time::seconds(1));
+ Locker l7(t, mtx_);
+ Locker l8(boost::posix_time::seconds(1), mtx_);
+ l5.lock_until(t);
+ l5.lock_for(boost::posix_time::seconds(1));
+ if (l5.try_lock_until(t)) return;
+ if (l5.try_lock_for(boost::posix_time::seconds(1))) return;
}
lockable_type mtx_;
+ system_time t;
};
- }}
-[section Template Class `LockerConcept<>`]
+
+[endsect]
+[section Template Class `UniqueLockerConcept<>`]
template <typename Locker>
- struct LockerConcept {
- typedef typename lockable_type<Locker>::type lockable_type;
- BOOST_CONCEPT_USAGE(LockerConcept) {
- Locker l(mtx_);
+ struct UniqueLockerConcept {
+ BOOST_CONCEPT_ASSERT((TimedLockerConcept<Locker>));
+
+ BOOST_CONCEPT_USAGE(UniqueLockerConcept) {
+ }
+ };
+
+[endsect]
+[section Template Class `SharedLockerConcept<>`]
+
+ template <typename Locker>
+ struct SharedLockerConcept {
+ BOOST_CONCEPT_ASSERT((TimedLockerConcept<Locker>));
+
+ BOOST_CONCEPT_USAGE(SharedLockerConcept) {
+ }
+ };
+
+[endsect]
+[section Template Class `UpgradeLockerConcept<>`]
+
+ template <typename Locker>
+ struct UpgradeLockerConcept {
+ BOOST_CONCEPT_ASSERT((TimedLockerConcept<Locker>));
+
+ BOOST_CONCEPT_USAGE(UpgradeLockerConcept) {
}
- lockable_type mtx_;
};
[endsect]
Modified: sandbox/synchro/libs/synchro/doc/reference/monitor.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/reference/monitor.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/reference/monitor.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -11,98 +11,81 @@
namespace boost { namespace synchro {
template <
- typename Lockable,
- class Condition,
- class ConditionBoosted
+ typename Lockable=thread_mutex
+ , class Condition=condition_safe<typename best_condition<Lockable>::type >
+ , typename ScopeTag=typename scope_tag<Lockable>::type
>
class exclusive_monitor;
- }}
-
-[section Template Class `exclusive_lockable_adapter<>`]
+ template <
+ typename Lockable=thread_shared_mutex,
+ , class Condition=condition_safe<typename best_condition_any<Lockable>::type >
+ , typename ScopeTag=typename scope_tag<Lockable>::type
+ >
+ class shared_monitor;
-The boost::mutex and boost:interprocess mutex family classes are a non-polymorphic classes that encapsulates a system primitive and portion of C API. Clearly, many of the synchronisation
-primitives support common operations, and hence a Concept.
-The ExclusiveLockable class can be used with the Boost.ConceptCheck in templates that work with a exclusive synchronisation.
-
-LockableConcept object supports the basic features required to delimit a critical region. Supports the basic lock, unlock and try_lock functions and defines the lock traits
-
- template <typename Lockable>
- struct LockableConcept {
- typedef typename category_tag<Lockable>::type category;
- typedef typename timed_interface_tag<Lockable>::type timed_interface;
- typedef typename reentrancy_tag<Lockable>::type reentrancy;
- typedef typename scope_tag<Lockable>::type scope;
- typedef typename lifetime_tag<Lockable>::type lifetime;
- typedef typename naming_tag<Lockable>::type naming;
-
- BOOST_CONCEPT_USAGE(LockableConcept) {
- l.lock();
- l.unlock();
- l.try_lock();
- }
- Lockable l;
- };
+ template <
+ typename Lockable=thread_mutex
+ , typename lock_tag=typename category_tag<Lockable>::type
+ , typename ScopeTag=typename scope_tag<Lockable>::type
+ >
+ struct monitor;
+ }}
-[endsect]
-[section Template Class `timed_lockable_adapter<>`]
-TimedLockableConcept object extends ExclusiveLockConcept with the timed_lock function
+[section Template Class `exclusive_monitor<>`]
- template <typename Lockable>
- struct TimedLockableConcept {
- BOOST_CONCEPT_ASSERT((LockableConcept<Lockable>));
-
- BOOST_CONCEPT_USAGE(TimedLockableConcept) {
- l.timed_lock(t);
- // l.timed_lock(1);
- }
- Lockable l;
- system_time t;
- };
+ template <
+ typename Lockable=thread_mutex
+ , class Condition=condition_safe<typename best_condition<Lockable>::type >
+ , typename ScopeTag=typename scope_tag<Lockable>::type
+ >
+ class exclusive_monitor : protected lockable_adapter<Lockable> {
+ BOOST_CONCEPT_ASSERT((LockableConcept<Lockable>));
+ protected:
+ typedef Condition condition;
+ typedef condition_unique_locker<Lockable, Condition, ScopeTag> synchronizer;
+ };
[endsect]
-[section Template Class `shared_lockable_adapter<>`]
-
-ShareLockableConcept object extends ExclusiveTimedLockConcept with the lock_shared, timed_lock_shared, try_lock_shared and unlock_shared functions
-
- template <typename Lockable>
- struct ShareLockableConcept {
- BOOST_CONCEPT_ASSERT((TimedLockableConcept<Lockable>));
-
- BOOST_CONCEPT_USAGE(ShareLockableConcept) {
- l.lock_shared();
- l.timed_lock_shared(t);
- l.try_lock_shared();
- l.unlock_shared();
- }
- Lockable l;
- system_time t;
- };
+[section Template Class `shared_monitor<>`]
+ template <
+ typename Lockable=thread_shared_mutex,
+ , class Condition=condition_safe<typename best_condition_any<Lockable>::type >
+ , typename ScopeTag=typename scope_tag<Lockable>::type
+ >
+ class shared_monitor : protected lockable_adapter<Lockable> {
+ BOOST_CONCEPT_ASSERT((LockableConcept<Lockable>));
+ protected:
+ typedef Condition condition;
+ typedef condition_unique_locker<Lockable, Condition, ScopeTag> synchronizer;
+ typedef condition_shared_locker<Lockable, Condition, ScopeTag> shared_synchronizer;
+ };
[endsect]
-[section Template Class `upgrade_lockable_adapter<>`]
-
-UpgradeLockableConcept object extends SharableLockableConcept with the lock_upgrade, timed_lock_upgrade, unlock_upgrade_and_lock, unlock_and_lock_shared and unlock_upgrade_and_lock_shared functions.
-
-
- template <typename Lockable>
- struct UpgradeLockableConcept {
- BOOST_CONCEPT_ASSERT((ShareLockableConcept<Lockable>));
-
- BOOST_CONCEPT_USAGE(UpgradeLockableConcept) {
- l.lock_upgrade();
- l.timed_lock_upgrade(t);
- l.unlock_upgrade_and_lock();
- l.unlock_and_lock_upgrade();
- l.unlock_and_lock_shared();
- l.unlock_upgrade_and_lock_shared();
- }
- Lockable l;
- system_time t;
- };
+[section Template Class `monitor<>`]
+ template <
+ typename Lockable=thread_mutex
+ , typename lock_tag=typename category_tag<Lockable>::type
+ , typename ScopeTag=typename scope_tag<Lockable>::type
+ > struct monitor;
+
+ template <typename Lockable, typename ScopeTag>
+ struct monitor<Lockable, exclusive_lock_tag, ScopeTag>
+ : protected exclusive_monitor<Lockable, ScopeTag>
+ {};
+
+ template <typename Lockable, typename ScopeTag>
+ struct monitor<Lockable, sharable_lock_tag, ScopeTag>
+ : protected shared_monitor<Lockable, ScopeTag>
+ {};
+
+ template <typename Lockable, typename ScopeTag>
+ struct monitor<Lockable, upgradable_lock_tag, ScopeTag>
+ : protected shared_monitor<Lockable, ScopeTag>
+ {};
[endsect]
Modified: sandbox/synchro/libs/synchro/doc/reference/null_mutex.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/reference/null_mutex.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/reference/null_mutex.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -11,67 +11,67 @@
[section:null_mutex_hpp Header `<boost/synchro/null_mutex.hpp>`]
[/==========================================================================================]
-namespace boost { namespace synchro {
-class null_condition;
-class null_mutex : public lock_traits_base<
- mono_threaded_tag,
- upgradable_lock_tag,
- recursive_tag,
- has_timed_interface_tag,
- kernel_lifetime_tag,
- anonymous_tag,
- void
->
-{
- null_mutex(const null_mutex&);
- null_mutex &operator= (const null_mutex&);
-public:
- typedef null_condition condition_type;
- typedef null_condition condition_any_type;
+ namespace boost { namespace synchro {
+ class null_condition;
+ class null_mutex : public lock_traits_base<
+ mono_threaded_tag,
+ upgradable_lock_tag,
+ recursive_tag,
+ has_timed_interface_tag,
+ kernel_lifetime_tag,
+ anonymous_tag,
+ void
+ >
+ {
+ null_mutex(const null_mutex&);
+ null_mutex &operator= (const null_mutex&);
+ public:
+ typedef null_condition condition_type;
+ typedef null_condition condition_any_type;
- null_mutex();
- ~null_mutex();
+ null_mutex();
+ ~null_mutex();
- void lock();
- bool try_lock();
- bool try_lock_until(const boost::posix_time::ptime &);
- template<typename TimeDuration>
- bool try_lock_for(TimeDuration const & relative_time);
+ void lock();
+ bool try_lock();
+ bool try_lock_until(const boost::posix_time::ptime &);
+ template<typename TimeDuration>
+ bool try_lock_for(TimeDuration const & relative_time);
- void unlock();
+ void unlock();
- void lock_shared(){};
- bool try_lock_shared();
- bool try_lock_shared_until(const boost::posix_time::ptime &);
- template<typename DurationType>
- bool try_lock_shared_for(DurationType const& rel_time)
+ void lock_shared(){};
+ bool try_lock_shared();
+ bool try_lock_shared_until(const boost::posix_time::ptime &);
+ template<typename DurationType>
+ bool try_lock_shared_for(DurationType const& rel_time)
- void unlock_shared();
+ void unlock_shared();
- void lock_upgrade();
- bool try_lock_upgrade();
- bool timed_lock_upgrade(boost::posix_time::ptime const &);
+ void lock_upgrade();
+ bool try_lock_upgrade();
+ bool timed_lock_upgrade(boost::posix_time::ptime const &);
- void unlock_upgrade();
+ void unlock_upgrade();
- void unlock_and_lock_upgrade();
+ void unlock_and_lock_upgrade();
- void unlock_and_lock_shared();
+ void unlock_and_lock_shared();
- void unlock_upgrade_and_lock_shared();
+ void unlock_upgrade_and_lock_shared();
- void unlock_upgrade_and_lock();
+ void unlock_upgrade_and_lock();
- bool try_unlock_upgrade_and_lock();
+ bool try_unlock_upgrade_and_lock();
- bool timed_unlock_upgrade_and_lock(const boost::posix_time::ptime &);
+ bool timed_unlock_upgrade_and_lock(const boost::posix_time::ptime &);
- bool try_unlock_share_and_lock();
+ bool try_unlock_share_and_lock();
- bool try_unlock_share_and_lock_upgrade();
-};
+ bool try_unlock_share_and_lock_upgrade();
+ };
-}}
+ }}
[endsect]
Modified: sandbox/synchro/libs/synchro/doc/reference/poly_lock.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/reference/poly_lock.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/reference/poly_lock.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -11,14 +11,14 @@
[section:poly_lock_hpp Header `<boost/synchro/poly/lock.hpp>`]
[/==========================================================================================]
-namespace boost { namespace synchro {
- namespace poly {
- struct exclusive_lock;
- struct timed_lock;
- struct sharable_lock;
- struct upgradable_lock;
- }
-}}
+ namespace boost { namespace synchro {
+ namespace poly {
+ struct exclusive_lock;
+ struct timed_lock;
+ struct sharable_lock;
+ struct upgradable_lock;
+ }
+ }}
[section Abstract Class `exclusive_lock`]
Modified: sandbox/synchro/libs/synchro/doc/reference/process_synchronization_family.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/reference/process_synchronization_family.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/reference/process_synchronization_family.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -22,11 +22,11 @@
struct process_synchronization_family {
typedef boost::synchro::interprocess_mutex mutex_type;
typedef boost::synchro::interprocess_recursive_mutex recursive_mutex_type;
- typedef boost::synchro::interprocess_mutex timed_mutex_type;
+ typedef boost::synchro::interprocess_mutex timed_mutex_type;
typedef boost::synchro::interprocess_recursive_mutex recursive_timed_mutex_type;
- typedef boost::synchro::interprocess_upgradable_mutex shared_mutex_type;
- typedef boost::interprocess::interprocess_condition condition_type;
- typedef boost::interprocess::interprocess_condition condition_any_type;
+ typedef boost::synchro::interprocess_upgradable_mutex shared_mutex_type;
+ typedef boost::interprocess::interprocess_condition condition_type;
+ typedef boost::interprocess::interprocess_condition condition_any_type;
};
Modified: sandbox/synchro/libs/synchro/doc/reference/thread_synchronization_family.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/reference/thread_synchronization_family.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/reference/thread_synchronization_family.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -17,12 +17,13 @@
struct thread_synchronization_family
{
- typedef boost::mutex mutex_type;
- typedef boost::recursive_mutex recursive_mutex_type;
- typedef boost::timed_mutex timed_mutex_type;
- typedef boost::recursive_timed_mutex recursive_timed_mutex_type;
- typedef boost::shared_mutex shared_mutex_type;
- typedef boost::condition_variable_any condition_variable_type;
+ typedef thread_mutex mutex_type;
+ typedef thread_recursive_mutex recursive_mutex_type;
+ typedef thread_timed_mutex timed_mutex_type;
+ typedef thread_recursive_timed_mutex recursive_timed_mutex_type;
+ typedef thread_shared_mutex shared_mutex_type;
+ typedef boost::condition_variable condition_type;
+ typedef boost::condition_variable_any condition_type_any;
};
Modified: sandbox/synchro/libs/synchro/doc/references.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/references.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/references.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -6,36 +6,47 @@
/]
[section:ext_references References]
-
[variablelist
[
- [[@http://www.ddj.com/cpp/184403766
- [*volatile - Multithreaded Programmer's Best Friend]]]
- [Andrei Alexandrescu.
- ]
+ [ [*Toward Simplified Parallel Support in C++]]
+ [Justin E. Gottschlich & Paul J. Rogers, 2009 - Not yet published]
]
-
[
- [[@http://www.informit.com/articles/article.aspx?p=25298
- [*Multithreading and the C++ Type System]]]
- [Andrei Alexandrescu.]
+ [[@http:/www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1883.pdf
+ [*N1833 - Preliminary Threading Library Proposal for TR2]]]
+ [Kevlin Henney, 2005]
]
[
+ [[@http://www.two-sdg.demon.co.uk/curbralan/papers/accu/MoreC++Threading.pdf
+ [*More C++ Threading - From Procedural to Generic, by Example]]]
+ [Kevlin Henney]
+]
+[
[[@http://www.two-sdg.demon.co.uk/curbralan/papers/accu/C++Threading.pdf
[*C++ Threading - A Generic-Programming Approach]]]
- [Kevlin Henney]
+ [Kevlin Henney, April 16, 2004]
]
[
- [[@http://www.two-sdg.demon.co.uk/curbralan/papers/accu/MoreC++Threading.pdf
- [*More C++ Threading - From Procedural to Generic, by Example]]]
- [Kevlin Henney]
+ [[@http://www.informit.com/articles/article.aspx?p=25298
+ [*Multithreading and the C++ Type System]]]
+ [Andrei Alexandrescu, Febraury 8, 2002]
+]
+[
+ [[@http://www.ddj.com/cpp/184403766 [*volatile - Multithreaded Programmer's Best Friend]]]
+ [Andrei Alexandrescu, Febraury 1, 2001]
+]
+
+[
+ [[@http://www.two-sdg.demon.co.uk/curbralan/papers/AsynchronousC++.pdf
+ [*Asynchronous C++]]]
+ [Kevlin Henney, September 9, 1996.]
+
]
[
[[@http://www.cs.wustl.edu/~schmidt/PDF/ACE-concurrency.pdf
- [*An OO Encapsulation of Lightweight OS
-Concurrency Mechanisms in the ACE Toolkit]]]
+ [*An OO Encapsulation of Lightweight OS Concurrency Mechanisms in the ACE Toolkit]]]
[Douglas C. Schmidt]
]
@@ -45,6 +56,7 @@
[Douglas C. Schmidt]
]
+
]
[endsect]
Modified: sandbox/synchro/libs/synchro/doc/synchro.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/synchro.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/synchro.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -53,6 +53,7 @@
[import ../../../boost/synchro/lockable_concepts.hpp]
[import ../../../boost/synchro/lockable_adapter.hpp]
[import ../../../boost/synchro/monitor.hpp]
+[import ../../../boost/synchro/lockers.hpp]
[import ../../../boost/synchro/thread_synchronization_family.hpp]
[import ../../../boost/synchro/conc/concurrent_component.hpp]
Modified: sandbox/synchro/libs/synchro/doc/tutorial.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/tutorial.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/tutorial.qbk 2009-02-16 03:17:51 EST (Mon, 16 Feb 2009)
@@ -20,1146 +20,4 @@
[include tutorial/external_locking.qbk]
[include tutorial/rendezvous.qbk]
-[/
-[section Internal Locking--Monitors]
-[section Concurrent execution of components]
-
-From here a component is a model of the Callable concept.
-
-Concurrent execution of a component may be described by means of the boost::thread:
-```boost::thread thread1(S);```
-where `S` is a reference to a component. The meaning of this expression is that execution
-of `S()` will take place concurrently with the execution of the component
-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:
-
-[BankAccount_ussage]
-
-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 do not
-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.
-
-[IL_BankAccount_BankAccount_mutex]
-
-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 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 (Boost.Thread lock_guard) we can simplify a lot this using the scoped locks.
-
-[BankAccount_raii]
-
-[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. We shall therefore introduce
-an abstraction that makes it easier to define such objects. The following class
-describes a so-called monitor pattern.
-
-[monitor]
-
-Depending on the lock category we have exclusive monitors and shared monitors
-[exclusive_monitor]
-[shared_monitor]
-
-A monitor object behaves like a ExclusiveLockable object but only for the inheriting classes.
-Protected inheritance from exclusive_lockable_adapter provide to all the derived classes all ExclusiveLockable 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:
-
-[BankAccount]
-
-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:
-
-[sync_buffer_schema]
-
-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:
-
-[sync_buffer_boost_thread_style]
-
-The Monitor class replace the nested synchronizer unique_lock with the class `condition_locker`
-for this purpose:
-
-[condition_locker]
-
-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`.
-
-[sync_buffer_monitor]
-
-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]
-
-[endsect]
-[endsect]
-
-
-[section External Locking -- `strict_locker` and `externally_locked` classes]
-
-
-This tutorial is an adaptation of the article 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.
-
-[IL_BankAccount_BankAccount]
-
-
-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:
-
-[IL_BankAccount_ATMWithdrawal]
-
-
-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:
-
-[IL_BankAccount_ATMWithdrawal_do_not_compile]
-
-
-Notice that the code above do not 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
-
-[IL_Lockable_BancAccount_BankAccount_explicit]
-
-or inheriting from a class which add these lockable functions.
-
-The `exclusive_lockable_adapter` class
-
-[exclusive_lockable_adapter]
-
-The `BankAccount` class result now in
-
-[IL_Lockable_BancAccount_BankAccount_inherit]
-
-and the code that do not comiles becomes
-
-[IL_Lockable_BancAccount_ATMWithdrawal]
-
-
-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`.
-
-[IL_Rec_Lockable_BancAccount_BankAccount]
-
-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 exclusive_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_locker`. Essentially, a `strict_locker`'s role is only to live on the
-stack as an automatic variable. `strict_locker` must adhere to a non-copy and non-alias policy.
-`strict_locker` 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_locker` are not intended to be
-allocated on the heap. `strict_locker` avoids aliasing by using a slightly less orthodox and less well-known
-technique: disable address taking.
-
-[strict_locker]
-
-[/ template <typename Lockable>
- class strict_locker : private boost::noncopyable {
- public:
- typedef Lockable lockable_type;
- typedef typename lock_traits<lockable_type>::lock_error bad_lock;
- explicit strict_locker(lockable_type& obj) : obj_(obj) {
- obj.lock();
- }
- ~strict_locker() { obj_.unlock(); }
-
- typedef bool (strict_locker::*bool_type)();
- operator bool_type() const
- {
- return &strict_locker::owns_lock;
- }
- bool operator!() const { return false; }
- bool owns_lock() const { return true; }
- private:
- lockable_type& obj_;
- private:
- /// disable default constructor
- strict_locker();
- /// disable aliasing
- BOOST_NON_ALIAS(strict_locker);
- /// disable heap allocation
- BOOST_NON_HEAP_ALLOCATED();
- };
-]
-
-Silence can be sometimes louder than words-what's forbidden to do with a `strict_locker` is as important as
-what you can do. Let's see what you can and what you cannot do with a `strict_locker` 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_lock<BankAccount> myLock(myAccount); // ok
-
-* You cannot copy `strict_locker`s to one another. In particular, you cannot pass `strict_locker`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_locker`s by reference to and from functions:
-
- // ok, Foo returns a reference to strict_lock<BankAccount>
- extern strict_locker<BankAccount>& Foo();
- // ok, Bar takes a reference to strict_lock<BankAccount>
- extern void Bar(strict_locker<BankAccount>&);
-
-* You cannot allocate a `strict_locker` on the heap. However, you still can put `strict_locker`s on the heap
- if they're members of a class.
-
- strict_locker<BankAccount>* pL =
- new strict_lock<BankAccount>(myAcount); //error!
- // operator new is not accessible
- class Wrapper {
- strict_locker memberLock_;
- ...
- };
- Wrapper* pW = new Wrapper; // ok
-
-(Making `strict_locker` a member variable of a class is not recommended. Fortunately, disabling copying
- and default construction makes `strict_locker` quite an unfriendly member variable.)
-
-* You cannot take the address of a `strict_locker` object. This interesting feature, implemented by
- disabling unary operator&, makes it very unlikely to alias a `strict_locker` object. Aliasing is still
- possible by taking references to a `strict_locker`:
-
- strict_locker<BankAccount> myLock(myAccount); // ok
- strict_locker<BankAccount>* pAlias = &myLock; // error!
- // strict_lock<BankAccount>::operator& is not accessible
- strict_locker<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_locker` 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_locker`, 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 exclusive_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_locker` being a template gives extra safety compared to a straight
-polymorphic approach. In such a design, BankAccount would derive from a Lockable interface.
-`strict_locker` 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_locker` object would only tell
-that some object derived from Lockable is currently locked. In the template 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_locker` 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_locker` class template. The `strict_lock<T>::get_lockable` function returns
-a reference to the locked object.
-
- template <class T> class strict_lock {
- ... as before ...
- public:
- T* get_lockable() const { return &obj_; }
- };
-
-Second, BankAccount needs to compare the locked object against this:
-
- class BankAccount {
- : public exclusive_lockable_adapter<boost::recursive_mutex>
- int balance_;
- public:
- void Deposit(int amount, strict_lock<BankAccount>& guard) {
- // Externally locked
- if (!guard.is_locking(*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 exclusive_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.
-[c++]
-
-[externally_locked]
-
-`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_locker<Owner>` object.
-
-Instead of making `checkingAcct_` and `savingsAcct_` of type `BankAccount`, `AccountManager` holds objects of
-type `externally_locked<BankAccount, AccountManager>`:
-
-[AccountManager]
-
-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:
-
-[Checking2Savings]
-
-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 lockers]
-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 do not compiles:
-
-[AMoreComplicatedChecking2Savings_DO_NOT_COMPILE]
-
-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`.
-
-[AMoreComplicatedChecking2Savings_DO_NOT_COMPILE2]
-
-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 locker class.
-The drawback is that instead of having only one strict locker 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 meta-function that check that the Locker parameter is a strict locker.
-The problem is that we can not really check this or can we?. The `is_strict_locker` meta-function must be specialized by the strict locker
-developer. We need to believe it "sur parolle".
-The advantage is that now we can manage with more than two strict lockers without changing our code. This is really nice.
-
-Now we need to state that both classes are `strict_locker`s.
-
- template <typename Locker>
- struct is_strict_locker : mpl::false_ {};
-
- template <typename Lockable>
- struct is_strict_locker<strict_locker<Lockable> > : mpl::true_ {}
-
- template <typename Locker>
- struct is_strict_locker<nested_strict_locker<Locker> > : mpl::true_ {}
-
-
-Well let me show how this `nested_strict_locker` class looks like
-and the impacts on the `externally_locked` class and the `AccountManager::AMoreComplicatedFunction` function.
-
-First `nested_strict_locker` 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 `locker_traits`.
-
-[nested_strict_locker]
-
-The `externally_locked` get function is now a template function taking a Locker as parameters instead of a
-`strict_locker`. We can add test in debug mode that ensure that the Lockable object is locked.
-
-[externally_locked_any]
-
-The `AccountManager::AMoreComplicatedFunction` function needs only to replace the `strict_locker` by a
-`nested_strict_locker`.
-
-[AMoreComplicatedChecking2Savings]
-
-[endsect]
-
-[endsect]
-
-[section Concurrent components]
-
-[section Direct communication between components]
-In the previous section we have described a mechanism for concurrent components
-to communicate through shared objects. In many cases it appears more
-natural for concurrent components to communicate directly instead of using
-shared objects. Consider the following example:
-
-[synchronized_communication_between_components_schema]
-
-
-Here the concurrent components `S` and `R` call operations on each other. The
-state of `S` may, however, not be meaningful when `R` executes `m`, and vice versa.
-In the following sections we will introduce abstractions for making it possible
-to synchronize such communication.
-
-[section Synchronized communication between components]
-
-In this section we will introduce the notion of synchronized execution of objects.
-A component `S` may request execution of a member function of a component
-`R`. The component `R` must accept that the request can be fulfilled.
-Synchronized communication is described in a generic class concurrent_component.
-A `concurrent_component` defines the notion of a `port` for controlling the communication. A
-`port` has a nested `synchronizer` class for defining operations controlled by the
-`port`; it also has an accept operation for signaling that an operation associated
-with the `port` can be executed. The `concurrent_component` has the following structure:
-
-[port]
-
-The following object illustrates two communicating `concurrent_component`s:
-
-[synchronized_communication_between_components]
-
-The concurrent_component `S` may execute a request, which is a normal remote procedure call:
-
-```
- e2 = r_.m(e1);
-```
-
-Since `m` is a synchronized port operation, the execution of M has to be accepted
-by R before it is executed.
-For M to be accepted, R must execute an accept, which has the following
-form:
-
-```
- port::accept(p);
-```
-
-The communication is carried out when `S` is executing `r_.m` and `R` is executing
-`port::accept(p)`. Both `S` and `R` are blocked until the communication takes place. A
-communication has the effect that `S` and `R` together execute the evaluation: `e2 = r_.m(e1);`
-
-This takes place as follows:
-
-# When S executes `e2 = r_.m(e1)`, `S` is now ready to execute the
-internals of the `R::m`.
-# When `R` executes `port::accept(p)`, `R` has signaled that the internals of a function
-protected with `port::synchronizer _(p)`; may be executed. `R` will wait until such an
-execution has taken place.
-# When both `R` and `S` are ready, the internals of `R::m` can be executed.
-# When the internals of `R::m` has been executed, `R` will continue execution. In
-addition, a possible return of `R::m` is assigned to `e2`.
-
-The object `S` executing `r_.m()` is called the sender, and the object `R` having `m` as
-an operation is called the receiver.
-
-In the following example, two systems `Prod` and `Cons` communicate via
-a single element buffer represented by a `SingleBuf` concurrent_component. The `SingleBuf`
-concurrent_component alternates between accepting a `Push` and a `Pull`:
-
-[SingleBuf]
-
-[endsect]
-[section Ports controlling several operations]
-
-It is possible to associate more than one operation with a port.
-
-The `Master`-concurrent_component transmits a sequence of values to the two
-`Slave`-systems, and each Slave-concurrent_component computes the sum of the values being
-received. Each value is received and accumulated by a synchronous execution
-of `Add`. A Slave object can be used according to the following protocol:
-
-# The `Clear` operation must be used to initiate a new sequence of summations.
-A `Clear` thus terminates any ongoing summation.
-# The `Add` operation accumulates a new value.
-# The `Result` operation returns the current sum.
-In the example, positive numbers are transmitted to `Slave1` and negative numbers
-are transmitted to `Slave2`.
-
-[Master_Slave_Slave]
-
-[Master_Slave_Master]
-
-
-[endsect]
-
-[section Restricted acceptance]
-
-An accept operation on a port signals that any object is allowed to execute
-an operation associated with the port. Sometimes it is desirable to restrict the
-possible objects that are allowed to execute a port operation. The restriction
-may specify that one specific object is allowed to execute a port operation, or
-it may specify that instances of a specific class are allowed to execute a port
-operation. These two types of restrictions are described in the following two
-sections.
-
-[*Object restriction]
-
-It is possible to restrict the sender of an operation by declaring the port as an
-instance of the `object_port` class. The `accept` operation of an `object_port`
-has a parameter which is a reference to the object that is allowed to
-execute a port operation. As C++ do not allows to recover the sender of an operation
-we need pass it as parameter.
-
-The syntax for this is:
-
- T S; //some component reference
- object_port request_;
- void Close(const concurrent_component_base* snd) {
- object_port::synchronizer _(request_, snd);
- // ...
- }
- //...
- object_port::accept(request_, sender_); // sender_ has been stored previously
-
-
-The example describes an abstract pattern for handling reservations
-of some kind. The reservations are supposed to be stored in some
-register. The actual way this is done is supposed to be described in sub-patterns
-of ReservationHandler. At most, one person at a time is allowed to
-make reservations. An agent making a reservation must perform the following
-steps:
-
-
-# The register must be locked by executing the Lock operation.
-# The agent may then perform one or more reservations using Reserve.
-# The agent terminates the reservation session by executing Close.
-
-The example includes a sketch of a handler for hotel reservations. The concurrent_component
-P describes a scenario of an agent making two hotel reservations.
-
-[ReservationHandler]
-
-[HotelResHandler]
-
-[HotelResHandler_main]
-
-[*Qualified restriction]
-
-The object_port described above makes it possible to ensure that only one
-specific concurrent_component may execute a port operation. It is often desirable to specify
-that a port operation may be executed by a restricted set of `concurrent_component`s. By using a
-port instantiated from `qualified_port`, it is possible to define port operations
-that may be executed by objects of a given class. The syntax for
-this is:
-
-
-Port operations associated with P may now be executed by an object which is
-an instance of T or inherits from T.
-
-The following example illustrates the use of a qualified port. The
-single buffer example is modified such that Push can only be executed by
-Producer objects and Pull can only be executed by Consumer objects.
-
-[QualfiedSingleBuf]
-
-[endsect]
-
-[endsect]
-
-[section Compound concurrent components]
-
-[section Indirect communication between internal concurrent components]
-
-Composition is a fundamental means for organizing objects. We have several
-examples of defining an object as compositions of other objects using part
-objects, references and block structure. We have also seen how the action part of
-an object may be composed of other objects. In this section we shall show how
-to construct compound systems that are concurrent_component objects consisting of several
-internal multiple action sequences.
-
-In Boost.Synchro the actions to be performed by a concurrent_component may be distributed
-among several internal systems. The internal systems may be more or less
-independent, and they may access common data (items in an enclosing concurrent_component),
-communicate with each other, communicate with external systems or
-control communication between external systems, and the enclosing concurrent_component.
-
-In the following, examples of such compound systems are described.
-For compound systems consisting of several internal concurrent systems,
-we are often interested in describing that execution of the outermost concurrent_component
-cannot terminate before execution of all inner systems have terminated. The
-outermost concurrent_component may have to do some initialization before executing the inner
-concurrent_component, and it may have to do some finalization (clean-up) when they
-have finished execution. The concurrent_component class has a concurrent_execution nested class that can
-be used for this purpose. concurrent_component can be used in the following way:
-
- concurrent_execution<S1,S2, S3)> conc(s1,s2, s3);
- conc();
- conc.join();
-
-[Histogram]
-
-[endsect]
-[section Communication with internal systems]
-
-[endsect]
-
-
-[endsect]
-
-[section Readers and writers problem]
-
-
-[endsect]
-
-
-
-
-
-
-
-
-
-
-
-
-
-[endsect]
-
-
-[section `volatile ` and `locking_ptr`]
-
-
-This tutorial is an adaptation of the article of Andrei Alexandrescu "`volatile` - Multithreaded
-Programmer's Best Friend"
-to the Boost library.
-
-[section Just a Little Keyword]
-
-Although both C and C++ Standards are conspicuously silent when it comes to threads, they do make a
-little concession to multithreading, in the form of the volatile keyword.
-
-Just like its better-known counterpart const, volatile is a type modifier. It's intended to be used
-in conjunction with variables that are accessed and modified in different threads. Basically, without
-volatile, either writing multithreaded programs becomes impossible, or the compiler wastes vast
-optimization opportunities. An explanation is in order.
-
-Consider the following code:
-
- class Gadget
- {
- public:
- void Wait()
- {
- while (!flag_)
- {
- sleep(1000); // sleeps for 1000 milliseconds
- }
- }
- void Wakeup()
- {
- flag_ = true;
- }
- ...
- private:
- bool flag_;
- };
-
-The purpose of `Gadget::Wait` above is to check the `flag_` member variable every second and return when that
-variable has been set to true by another thread. At least that's what its programmer intended, but, alas,
-Wait is incorrect. Suppose the compiler figures out that `sleep(1000)` is a call into an external library
-that cannot possibly modify the member variable `flag_`. Then the compiler concludes that it can cache `flag_`
-in a register and use that register instead of accessing the slower on-board memory. This is an excellent
-optimization for single-threaded code, but in this case, it harms correctness: after you call Wait for some
-`Gadget` object, although another thread calls Wakeup, Wait will loop forever. This is because the change of
-`flag_` will not be reflected in the register that caches `flag_`. The optimization is too ... optimistic.
-Caching variables in registers is a very valuable optimization that applies most of the time, so it would
-be a pity to waste it. C and C++ give you the chance to explicitly disable such caching. If you use the
-volatile modifier on a variable, the compiler won't cache that variable in registers -- each access will
-hit the actual memory location of that variable. So all you have to do to make Gadget's Wait/Wakeup combo
-work is to qualify `flag_` appropriately:
-
- class Gadget
- {
- public:
- ... as above ...
- private:
- volatile bool flag_;
- };
-
-Most explanations of the rationale and usage of `volatile` stop here and advise you to volatile-qualify the
-primitive types that you use in multiple threads. However, there is much more you can do with `volatile`,
-because it is part of C++'s wonderful type system.
-
-[endsect]
-[section Using `volatile` with User-Defined Types]
-
-You can volatile-qualify not only primitive types, but also user-defined types. In that case, `volatile`
-modifies the type in a way similar to const. (You can also apply const and `volatile` to the same type
-simultaneously.) Unlike `const`, `volatile` discriminates between primitive types and user-defined types.
-Namely, unlike classes, primitive types still support all of their operations (addition, multiplication,
-assignment, etc.) when volatile-qualified. For example, you can assign a non-volatile `int` to a `volatile` `int`,
-but you cannot assign a non-volatile object to a `volatile` object. Let's illustrate how `volatile` works on
-user-defined types on an example.
-
- class Gadget
- {
- public:
- void Foo() volatile;
- void Bar();
- ...
- private:
- std::tring name_;
- int state_;
- };
- ...
- Gadget regularGadget;
- volatile Gadget volatileGadget;
-
-If you think `volatile` is not that useful with objects, prepare for some surprise.
-
- volatileGadget.Foo(); // ok, volatile fun called for
- // volatile object
-
-
- regularGadget.Foo(); // ok, volatile fun called for
- // non-volatile object
- volatileGadget.Bar(); // error! Non-volatile function called for
- // volatile object!
-
-The conversion from a non-qualified type to its `volatile` counterpart is trivial. However, just as with
-const, you cannot make the trip back from `volatile` to non-qualified. You must use a cast:
-
- Gadget& ref = const_cast<Gadget&>(volatileGadget);
- ref.Bar(); // ok
-
-A volatile-qualified class gives access only to a subset of its interface, a subset that is under the
-control of the class implementer. Users can gain full access to that type's interface only by using a
-`const_cast`. In addition, just like constness, volatileness propagates from the class to its members
-(for example, `volatileGadget.name_` and `volatileGadget.state_` are `volatile` variables).
-
-[endsect]
-[section `volatile`, Critical Sections, and Race Conditions]
-
-The simplest and the most often-used synchronization device in multithreaded programs is the mutex.
-
-[/A mutex
-exposes the `lock` and `unlock` primitives. Once you call `lock` in some thread, any other thread calling
-`lock` will block. Later, when that thread calls `unlock`, precisely one thread blocked in an `lock` call
-will be released. In other words, for a given mutex, only one thread can get processor time in between a
-call to `lock` and a call to `unlock`. The executing code between a call to `lock` and a call to `unlock`
-is called a critical section. (Windows terminology is a bit confusing because it calls the mutex itself a
-critical section, while "mutex" is actually an inter-process mutex. It would have been nice if they were
-called thread mutex and process mutex.)
-/]
-Mutexes are used to protect data against race conditions. By
-definition, a race condition occurs when the effect of more threads on data depends on how threads are
-scheduled. Race conditions appear when two or more threads compete for using the same data. Because threads
-can interrupt each other at arbitrary moments in time, data can be corrupted or misinterpreted. Consequently,
-changes and sometimes accesses to data must be carefully protected with critical sections. In
-object-oriented programming, this usually means that you store a mutex in a class as a member variable and
-use it whenever you access that class' state. Experienced multithreaded programmers might have yawned
-reading the two paragraphs above, but their purpose is to provide an intellectual workout, because now we
-will link with the `volatile` connection. We do this by drawing a parallel between the C++ types' world and
-the threading semantics world.
-
-* Outside a critical section, any thread might interrupt any other at any time; there is no control, so
- consequently variables accessible from multiple threads are `volatile`. This is in keeping with the
- original intent of `volatile` -- that of preventing the compiler from unwittingly caching values used by
- multiple threads at once.
-* Inside a critical section defined by a mutex, only one thread has access. Consequently, inside a
- critical section, the executing code has single-threaded semantics. The controlled variable is not
- `volatile` anymore -- you can remove the `volatile` qualifier.
-
-In short, data shared between threads is conceptually `volatile` outside a critical section, and non-volatile
-inside a critical section. You enter a critical section by locking a mutex. You remove the `volatile`
-qualifier from a type by applying a `const_cast`. If we manage to put these two operations together, we
-create a connection between C++'s type system and an application's threading semantics. We can make the
-compiler check race conditions for us.
-
-[endsect]
-[section `locking_ptr`]
-
-We need a tool that collects a mutex acquisition and a `const_cast`. Let's develop a `locking_ptr` class template
-that you initialize with a volatile object obj and a mutex mtx. During its lifetime, a `locking_ptr` keeps mtx
-acquired. Also, `locking_ptr` offers access to the volatile-stripped obj. The access is offered in a smart
-pointer fashion, through operator-> and operator*. The `const_cast` is performed inside `locking_ptr`. The cast
-is semantically valid because `locking_ptr` keeps the mutex acquired for its lifetime. First, let's define the
-skeleton of a class `mutex` with which `locking_ptr` will work:
-
- class mutex
- {
- public:
- void lock();
- void unlock();
- ...
- };
-
-`locking_ptr` is templated with the type of the controlled variable and the exclusive lockable type.
-For example, if you want to
-control a Widget, you use a `locking_ptr<Widget> that you initialize with a variable of type `volatile` Widget.
-`locking_ptr`'s definition is very simple. `locking_ptr` implements an unsophisticated smart pointer. It focuses solely on collecting a `const_cast` and a critical section.
-
- template <typename T>
- class locking_ptr {
- public:
- // Constructors/destructors
- locking_ptr(volatile T& obj, mutex& mtx)
- : obj_(*const_cast<T*>(&obj)),
- mtx_(mtx)
- { mtx_.lock(); }
- ~locking_ptr()
- { mtx_->unlock(); }
- // Pointer behavior
- T& operator*()
- { return obj_; }
- T* operator->()
- { return &obj_; }
- private:
- T& obj_;
- mutex& mtx_;
- locking_ptr(const locking_ptr&);
- locking_ptr& operator=(const locking_ptr&);
- };
-
-In spite of its simplicity, `locking_ptr` is a very useful aid in writing correct multithreaded code. You should define objects that are shared between threads as volatile and never use `const_cast` with them -- always use `locking_ptr` automatic objects. Let's illustrate this with an example. Say you have two threads that share a vector<char> object:
-
- class SynchroBuf {
- public:
- void Thread1();
- void Thread2();
- private:
- typedef vector<char> BufT;
- volatile BufT buffer_;
- mutex mtx_; // controls access to buffer_
- };
-
-Inside a thread function, you simply use a `locking_ptr<BufT>` to get controlled access to the `buffer_` member variable:
-
- void SynchroBuf::Thread1() {
- locking_ptr<BufT> lpBuf(buffer_, mtx_);
- BufT::iterator i = lpBuf->begin();
- for (; i != lpBuf->end(); ++i) {
- ... use *i ...
- }
- }
-
-The code is very easy to write and understand -- whenever you need to use `buffer_`, you must create a `locking_ptr<BufT>` pointing to it. Once you do that, you have access to vector's entire interface. The nice part is that if you make a mistake, the compiler will point it out:
-
- void SynchroBuf::Thread2() {
- // Error! Cannot access 'begin' for a volatile object
- BufT::iterator i = buffer_.begin();
- // Error! Cannot access 'end' for a volatile object
- for (; i != lpBuf->end(); ++i) {
- ... use *i ...
- }
- }
-
-You cannot access any function of `buffer_` until you either apply a `const_cast` or use `locking_ptr`. The difference is that `locking_ptr` offers an ordered way of applying `const_cast` to volatile variables. `locking_ptr` is remarkably expressive. If you only need to call one function, you can create an unnamed temporary `locking_ptr` object and use it directly:
-
- unsigned int SynchroBuf::Size() {
- return locking_ptr<BufT>(buffer_, mtx_)->size();
- }
-
-
-[endsect]
-[section Back to Primitive Types]
-
-We saw how nicely `volatile` protects objects against uncontrolled access and how `locking_ptr` provides a simple and effective way of writing thread-safe code. Let's now return to primitive types, which are treated differently by `volatile`. Let's consider an example where multiple threads share a variable of type int.
-
- class Counter
- {
- public:
- ...
- void Increment() { ++ctr_; }
- void Decrement() { --ctr_; }
- private:
- int ctr_;
- };
-
-If Increment and Decrement are to be called from different threads, the fragment above is buggy. First, `ctr_` must be volatile. Second, even a seemingly atomic operation such as `++ctr_` is actually a three-stage operation. Memory itself has no arithmetic capabilities. When incrementing a variable, the processor:
-
-* Reads that variable in a register
-* Increments the value in the register
-* Writes the result back to memory
-
-This three-step operation is called RMW (Read-Modify-Write). During the Modify part of an RMW operation, most processors free the memory bus in order to give other processors access to the memory. If at that time another processor performs a RMW operation on the same variable, we have a race condition: the second write overwrites the effect of the first. To avoid that, you can rely, again, on `locking_ptr`:
-
- class Counter
- {
- public:
- ...
- void Increment() { ++*locking_ptr<int, boost::mutex>(ctr_, mtx_); }
- void Decrement() { --*locking_ptr<int, boost::mutex>(ctr_, mtx_); }
- private:
- volatile int ctr_;
- boost::mutex mtx_;
- };
-
-Now the code is correct, but its quality is inferior when compared to SynchroBuf's code. Why? Because with Counter, the compiler will not warn you if you mistakenly access `ctr_` directly (without locking it). The compiler compiles `++ctr_` if `ctr_` is volatile, although the generated code is simply incorrect. The compiler is not your ally anymore, and only your attention can help you avoid race conditions. What should you do then? Simply encapsulate the primitive data that you use in higher-level structures and use `volatile` with those structures. Paradoxically, it's worse to use `volatile` directly with built-ins, in spite of the fact that initially this was the usage intent of `volatile`!
-
-
-[endsect]
-[section `volatile` Member Functions]
-
-So far, we've had classes that aggregate `volatile` data members; now let's think of designing classes that in turn will be part of larger objects and shared between threads. Here is where `volatile` member functions can be of great help. When designing your class, you volatile-qualify only those member functions that are thread safe. You must assume that code from the outside will call the volatile functions from any code at any time. Don't forget: `volatile` equals free multithreaded code and no critical section; non-volatile equals single-threaded scenario or inside a critical section. For example, you define a class Widget that implements an operation in two variants -- a thread-safe one and a fast, unprotected one.
-
- class Widget
- {
- public:
- void Operation() volatile;
- void Operation();
- ...
- private:
- boost::mutex mtx_;
- };
-
-Notice the use of overloading. Now Widget's user can invoke Operation using a uniform syntax either for volatile objects and get thread safety, or for regular objects and get speed. The user must be careful about defining the shared Widget objects as `volatile`. When implementing a `volatile` member function, the first operation is usually to lock this with a `locking_ptr`. Then the work is done by using the non-volatile sibling:
-
- void Widget::Operation() volatile
- {
- locking_ptr<Widget,boost::mutex> lpThis(*this, mtx_);
- lpThis->Operation(); // invokes the non-volatile function
- }
-
-[endsect]
-[section Generic `locking_ptr`]
-
-The `locking_ptr` works with a mutex class. How to use it with other mutexes? We can make a more generic `locking_ptr` adding a Lockable template parameter. [/As the more common use will be `boos::mutex` this will be the default value]
-
-[locking_ptr]
-
-Every model of the ExclusiveLockable concept can be used as parameter.
-
-[endsect]
-
-[section Specific `locking_ptr` for lockable value types]
-
-When the value type is itself lockable we can simplify the `locking_ptr` as follows:
-
-[locking_ptr_lockable_value_type]
-
-
-
-[endsect]
-[endsect]
-
-]
[endsect]
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