|
Boost-Commit : |
Subject: [Boost-commit] svn:boost r51245 - in sandbox/synchro/libs/synchro: doc doc/tutorial example
From: vicente.botet_at_[hidden]
Date: 2009-02-14 07:43:07
Author: viboes
Date: 2009-02-14 07:43:05 EST (Sat, 14 Feb 2009)
New Revision: 51245
URL: http://svn.boost.org/trac/boost/changeset/51245
Log:
Boost.Synchro V0.0.0
Text files modified:
sandbox/synchro/libs/synchro/doc/index.html | 2
sandbox/synchro/libs/synchro/doc/introduction.qbk | 242 ++++++---
sandbox/synchro/libs/synchro/doc/introduction_traits_and_concepts.qbk | 30
sandbox/synchro/libs/synchro/doc/overview.qbk | 62 +-
sandbox/synchro/libs/synchro/doc/reference.qbk | 937 ++-------------------------------------
sandbox/synchro/libs/synchro/doc/references.qbk | 15
sandbox/synchro/libs/synchro/doc/synchro.qbk | 6
sandbox/synchro/libs/synchro/doc/tutorial.qbk | 142 ++----
sandbox/synchro/libs/synchro/doc/tutorial/external_locking.qbk | 263 +++-------
sandbox/synchro/libs/synchro/doc/tutorial/internal_locking.qbk | 120 +---
sandbox/synchro/libs/synchro/doc/tutorial/lockable.qbk | 103 +--
sandbox/synchro/libs/synchro/doc/tutorial/rendezvous.qbk | 134 +----
sandbox/synchro/libs/synchro/doc/tutorial/volatile_locking_ptr.qbk | 161 +-----
sandbox/synchro/libs/synchro/example/Master_Slave.cpp | 4
sandbox/synchro/libs/synchro/example/SingleBuf.cpp | 4
15 files changed, 513 insertions(+), 1712 deletions(-)
Modified: sandbox/synchro/libs/synchro/doc/index.html
==============================================================================
--- sandbox/synchro/libs/synchro/doc/index.html (original)
+++ sandbox/synchro/libs/synchro/doc/index.html 2009-02-14 07:43:05 EST (Sat, 14 Feb 2009)
@@ -3,7 +3,7 @@
<meta http-equiv="refresh" content="0; URL=html/index.html">
</head>
<body>
-Automatic redirection failed, please go to
+Automatic redirection failed, please go to
<a href="../../doc/html/luid.html">../../doc/html/luid.html</a>
</body>
</html>
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-14 07:43:05 EST (Sat, 14 Feb 2009)
@@ -8,82 +8,77 @@
[section:intro Introduction]
-[*Description]
+[include introduction_traits_and_concepts.qbk]
-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.
-
-[*Boost.Synchro] provides:
-
-* A uniforme usage of Boost.Thread and Boost.Interprocess synchronization mechanisms
-based on some lock concepts, lock traits and locking families.
-* semaphore, null_mutex and null_condition
-* Some lockers are also provided as `strict_locker`, `nested_strict_locker`,
-`condition_locker`, `locking_ptr`, `externally_locked`, `reverse_lock`.
-* 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.
-* In addition a polymorph lockable hierarchy is also included.
+[section Mapping the current mutexes (Boost.Thread and Boost/Interprocess) to the common concepts]
-This library is much more a compilation of what I have found in the literature
-that is not yet present in Boost.
+[endsect]
-Other abstractions that I would also like to see soon in Boost are futures, task
-schedulers, continuations, coroutines, non-premptyble threads, ...
+[section Condition lockable]
-[include introduction_traits_and_concepts.qbk]
+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.
-[section:conc Concurrent components]
+ class product_queue {
+ public:
+ ...
+ product *pull() {
+ guard.lock();
+ while(queue.empty())
+ guard.relock_on(not_empty);
+ product *pulled = queue.front();
+ queue.pop();
+ guard.unlock();
+ return pulled;
+ }
+ ...
+ };
+
+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.
+
+ class product_queue {
+ public:
+ ...
+ product *pull() {
+ guard.lock_when(not_empty, has_products(queue));
+ product *pulled = queue.front();
+ queue.pop();
+ guard.unlock();
+ return pulled;
+ }
+ ...
+ };
+
+[endsect]
-Concurrent components may interact in different ways: they may access
-the same objects by, for example, executing functions of
-these objects; or they may communicate directly by executing functions of
-each other.
-
-Concurrent execution of objects requires a mechanism
-for synchronizing the access to shared objects, just as direct communication
-between objects may require synchronization. The basic mechanism for synchronization in
-Boost.Threads and Boost.Interprocess are the well known mutex and condition_variables. Mutexes
-and condition variables are, however, only useful for very simple synchronization problems.
-The Synchro Library therefore 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. All the concurrency abstractions being introduced are defined by
-means of mutexes an conditions.
+[section Timeouts]
+
+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
+
+Use of timeouts can create more robust programs, by not blocking forever, but at the same time one needs to avoid annoyingly arbitrary limits.
[endsect]
+
[section:lockers Lockers]
-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.
+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.
-A locker is any object or function responsible for coordinating the use of lockable objects
+A locker is any object or function responsible for coordinating the use of lockable objects.
-* Lockers depend on lockable objects - which need not be locking primitives - and not vice-versa.
-This avoids cycles in the dependency graph
-* Lockers are applications of lockable objects and, as such, form a potentially unbounded family
-Most common role of lockers is for exception safety and programming convenience
+* Lockers depend on lockable objects - which need not be locking primitives - and not vice-versa. This avoids cycles in the dependency graph.
+* Lockers are applications of lockable objects and, as such, form a potentially unbounded family. Most common role of lockers is for exception safety and programming convenience
* Lockers execute-around the lock-unlock pairing.
-A locker defines an execution strategy for locking and unlocking that
-is automated by construction and destruction. It simplifies common
-use of locking, and does so in an exception-safe fashion. As such,
-lockers depend on the interface of lockables -e.g. lock and unlock
-- but lockables do not depend on lockers. The relationship is strictly
-layered, open and extensible: lockable types may be whole, externally
-locked objects against which existing lockers can be used; new lockers
-can be defined that work against existing lockable types.
+A locker defines an execution strategy for locking and unlocking that is automated by construction and destruction. It simplifies common use of locking, and does so in an exception-safe fashion. As such, lockers depend on the interface of lockables -e.g. lock and unlock - but lockables do not depend on lockers. The relationship is strictly layered, open and extensible: lockable types may be whole, externally locked objects against which existing lockers can be used; new lockers can be defined that work against existing lockable types.
+
+Substitutability between lockables and lockers does not make sense, so the constructor is always explicit. Implicit copyability is also disabled.
Boost.Thread and Boost.Interprocess defines already a good starting point with these lockers:
@@ -92,57 +87,109 @@
* `boost::share_lock` and `boost::interprocess::sharable_lock`
* `boost::upgrade_lock` and `boost::interprocess::upgradable_lock`.
+The problem is that even if these locker models the same model, there is no a single syntax.
+
+The library defines some locker adapters which take care of naming differences and that can be used like
+
+ 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.
+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
+
+ lock_guard(Lockable & m, boost::adopt_lock_t)
+
+We can say that lock_guard is a strict locker "sur parolle".
+
-The library provides two strict lockers
+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.
-* `strict_locker`
-* `neested_strict_locker`
+ bool is_locking(lockable_type* l) const;
+
+The library provides three strict lockers
+
+* `strict_locker`: is the basic strict locker
+* `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
and a meta function `is_strict_locker` which states if a locker is a strict locker
-Substitutability between lockables and lockers does not make sense,
-so the constructor is explicit. Implicit copyability is also disabled.
-strict_lockers are not Lockables.
+So as strict lockers do not provide lock/unlock functions they are not models of Lockable.
+
+[*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().
+
+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()) {
+ pulled = queue.front();
+ queue.pop();
+ }
+ return pulled;
+ }
+
+If we use interprocess mutexes the following line
+
+ boost::unique_lock<boost::mutex> scoped(guard, boost::try_to_lock);
+
+need to be changed 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:
+
+ typedef bool try_locker::*is_locked;
+ operator is_locked() const {
+ return locked ? &try_locker::locked : 0;
+ }
+
+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.
+
+ 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;
+ }
+
+The library defines a try_locker adapter which take care of naming differences and that can be used like
+
+ boost::synchro::unique_try_locker<Lockable> scoped(guard);
+
[*External lockers]
-An alternative or complementary approach to internal locking is
-to support external locking for an object - Multiple calls may be
-grouped within the same externally defined critical region.
-
-External locking has some associated risks for high-level objects.
-Incorrect usage can be too easy: a forgotten call to lock or unlock is
-more likely than with synchronisation primitives because the focus of
-using the object is on the rest of its non-Lockable interface, so it
-becomes easy to forget that to use the interface correctly also requires
-participation in a locking scheme.
-
-To some extent lockers can help, but such a co-operative scheme
-should only be employed when internal locking is too restricted for a
-given use, e.g. multiple operations must be performed together.
-Ideally, if such operations are common they should be defined
-internally locked and defined in the interface of the object as
-Combined Methods.
-Assuming that locks are re-entrant, external locking can be provided
-to complement the more encapsulated internal locking, i.e. by default
-if you want to call a single function you just call it and it
-automatically locks, but if you want to call multiple functions
-together you first apply an external lock.
+An alternative or complementary approach to internal locking is to support external locking for an object - Multiple calls may be grouped within the same externally defined critical region.
+
+External locking has some associated risks for high-level objects. Incorrect usage can be too easy: a forgotten call to lock or unlock is more likely than with synchronisation primitives because the focus of using the object is on the rest of its non-Lockable interface, so it becomes easy to forget that to use the interface correctly also requires participation in a locking scheme.
+
+To some extent lockers can help, but such a co-operative scheme should only be employed when internal locking is too restricted for a given use, e.g. multiple operations must be performed together. Ideally, if such operations are common they should be defined internally locked and defined in the interface of the object as Combined Methods.
-The library provides a `externally_locked` class that allows to access a
-externally locked class in a thread safe mode through strict lockers.
+Assuming that locks are re-entrant, external locking can be provided to complement the more encapsulated internal locking, i.e. by default if you want to call a single function you just call it and it automatically locks, but if you want to call multiple functions together you first apply an external lock.
-Where only external locking is used, a safe approach is needed for
-calling single functions easily. The library provides two classes
+The library provides a `externally_locked` class that allows to access a externally locked class in a thread safe mode through strict lockers.
+
+Where only external locking is used, a safe approach is needed for calling single functions easily. The library provides two classes
* `locking_ptr` and
* `on_dereference_locking_ptr`
+* `externally_locked`
[endsect]
@@ -150,4 +197,13 @@
[endsect]
+[section:conc Concurrent components]
+
+Concurrent components may interact in different ways: they may access the same objects by, for example, executing functions of these objects; or they may communicate directly by executing functions of each other.
+
+Concurrent execution of objects requires a mechanism for synchronizing the access to shared objects, just as direct communication between objects may require synchronization. The basic mechanism for synchronization in Boost.Threads and Boost.Interprocess are the well known mutex and condition_variables. Mutexes and condition variables are, however, only useful for very simple synchronization problems. The Synchro Library therefore 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. All the concurrency abstractions being introduced are defined by means of mutexes an conditions.
+
+[endsect]
+
+
[endsect]
\ No newline at end of file
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-14 07:43:05 EST (Sat, 14 Feb 2009)
@@ -8,28 +8,20 @@
[section:uniform Using thread, Interprocess and null synchronization mechanisms uniformly]
-One of the problems when doing multi threaded application with Bosst.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
-distinguishably 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.
+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.
[*Lock substitutability]
-The Boost (C++0x) mutexes have associated a category which form a subtyping hierarchy:
-ExclusiveLock <- SharableLock <- UpgradableLock
+The Boost (C++0x) mutexes have associated a category which form a sub-typing hierarchy:
- exclusive_lock <- sharable_lock <- upgradable_lock
+ ExclusiveLockable <- SharedLockable <- UpgradeLockable
[category_tag_hierarchy]
-Locking behaviour can be further cathegorized as:
+Locking behavior can be further categorized as:
* Re-entrancy: recursive or not
@@ -39,7 +31,7 @@
mono_threaded <- multi_threaded <- multi_process
-* Lifetime: The lifetime of a lock coudl be associated to the process, the kernel or the filesystem
+* Lifetime: The lifetime of a lock could be associated to the process, the kernel or the file-system
process_lifetime <- kernel_lifetime <- filesystem_lifetime
@@ -54,7 +46,7 @@
We can see these axes of variation expressed against some
-Boost synchronisation mechanisms (from now bip stands for boost::interprocess):
+Boost synchronization mechanisms (from now bip stands for boost::interprocess):
* boost::mutex: ExclusiveLock, non-recursive, has-not-timed-interface, multi-threaded
* boost::shared_mutex: UpgradableLock, non-recursive, has-timed-interface, multi-threaded
@@ -81,13 +73,13 @@
It is also possible to specify characteristics to perform a reverse lookup to find a primitive lock type, either by
exact match or by substitutable match.
-[*Synchrohronization familly]
+[*Synchronization family]
-A class that will do internal locking can be parameterized by the type of synchronization familly needed to achieve
+A class that will do internal locking can be parameterized by the type of synchronization family needed to achieve
the desired level of concurrency control. This depends of the usage scope of this class, and this can be
mono_threaded, multi_threaded, multi_process.
-For example the thread_synchronization_familly can be used to instantiate a message_queue class in a multi_threaded
+For example the thread_synchronization_family can be used to instantiate a message_queue class in a multi_threaded
environment, all public methods will be thread-safe, with the corresponding overhead that implies. In contrast, if a
null_synchronization_policy class is used to instantiate message_queue, all public methods will not be thread-safe,
and there will be no additional overhead.
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-14 07:43:05 EST (Sat, 14 Feb 2009)
@@ -1,5 +1,5 @@
[/
- (C) Copyright 2008 Vicente J Botet Escriba.
+ (C) Copyright 2008-2009 Vicente J Botet Escriba.
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt).
@@ -13,30 +13,35 @@
[heading Description]
[/==================]
-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.
+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.
[*Boost.Synchro] provides:
-* A uniforme usage of Boost.Thread and Boost.Interprocess synchronization mechanisms
-based on some lock concepts, lock traits and locking families.
-* semaphore, null_mutex and null_condition
-* Some lockers are also provided as `strict_locker`, `nested_strict_locker`,
-`condition_locker`, `locking_ptr`, `externally_locked`, `reverse_lock`.
-* 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.
-* In addition a polymorph lockable hierarchy is also included.
+* 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`,
+ * `condition_locker`,
+ * `locking_ptr`,
+ * `externally_locked`,
+ * `reverse_locker` and
+ * `nested_reverse_locker`.
+
+* 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.
-This library is much more a compilation of what I have found in the literature
-that is not yet present in Boost.
+* In addition a polymorph lockable hierarchy is also included.
-Other abstractions that I would also like to see soon in Boost are futures, task
-schedulers, continuations, coroutines, non-premptyble threads, ...
+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]
@@ -47,20 +52,13 @@
* Code is in `fixed width font` and is syntax-highlighted.
* Replaceable text that you will need to supply is in [~italics].
* If a name refers to a free function, it is specified like this:
- `free_function()`; that is, it is in code font and its name is followed by `()`
- to indicate that it is a free function.
-* If a name refers to a class template, it is specified like this:
- `class_template<>`; that is, it is in code font and its name is followed by `<>`
- to indicate that it is a class template.
+ `free_function()`; that is, it is in code font and its name is followed by `()` to indicate that it is a free function.
+* If a name refers to a class template, it is specified like this: `class_template<>`; that is, it is in code font and its name is followed by `<>` to indicate that it is a class template.
* If a name refers to a function-like macro, it is specified like this: `MACRO()`;
- that is, it is uppercase in code font and its name is followed by `()` to
- indicate that it is a function-like macro. Object-like macros appear without the
- trailing `()`.
-* Names that refer to /concepts/ in the generic programming sense are
- specified in CamelCase.
+ that is, it is uppercase in code font and its name is followed by `()` to indicate that it is a function-like macro. Object-like macros appear without the trailing `()`.
+* Names that refer to /concepts/ in the generic programming sense are specified in CamelCase.
-[note In addition, notes such as this one specify non-essential information that
-provides additional background or rationale.]
+[note In addition, notes such as this one specify non-essential information that provides additional background or rationale.]
Finally, you can mentally add the following to any code fragments in this document:
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-14 07:43:05 EST (Sat, 14 Feb 2009)
@@ -7,914 +7,81 @@
[section Reference]
-[/==========================================================================================]
-[section Header `<boost/synchro/lock_generator.hpp>`]
-[/==========================================================================================]
-
-[endsect]
-
-[/==========================================================================================]
-[section Header `<boost/synchro/lockable_concept.hpp>`]
-[/==========================================================================================]
-
-
-[section Class `exclusive_lockable`]
-[*Synopsis]
-Polimorphic exclusive lock interface.
-
-[*Description]
-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 interfaces. In some cases a more
-general interface is useful.
-The exclusive_lockable interface class may be used explicitly as a base class for a
-class supporting exclusive synchronisation.
-
-[*`exclusive_lock` public member functions]
-
-# `;`
-
-[endsect]
-
-[endsect]
-
-[/==========================================================================================]
-[section Header `<boost/synchro/locker_concept.hpp>`]
-[/==========================================================================================]
-
- template <typename Lockable> struct LockableConcept;
- template <typename Lockable> struct TimedLockableConcept;
- template <typename Lockable> struct ShareLockableConcept;
- template <typename Lockable> struct UpgradeLockableConcept;
-
-
-[section Template Class `LockableConcept`]
-
-
-
-[endsect]
-
-[section Template Class `TimedLockableConcept`]
-
-
-
-[endsect]
-[section Template Class `ShareLockableConcept`]
-
-
-
-[endsect]
-[section Template Class `UpgradeLockableConcept`]
-
-
-
-[endsect]
-
-[endsect]
-[/==========================================================================================]
-[section Header `<boost/synchro/make_lockable.hpp>`]
-[/==========================================================================================]
-
- template <typename Lockable> class make_exclusive_lockable;
- template <typename TimedLock> class make_timed_lockable;
- template <typename SharableLock> class make_share_lockable;
- template <typename UpgradableLock> class make_upgrade_lockable;
- template <typename Lockable, typename category, typename timed_interface>
- struct make_lockable;
-
-
-
-[endsect]
-
-[/==========================================================================================]
-[section Header `<boost/synchro/monitor.hpp>`]
-[/==========================================================================================]
-
-template <
- typename Lockable,
- class Condition,
- class ConditionBoosted
->
-class exclusive_monitor;
-
-[endsect]
-
-[/==========================================================================================]
-[section Header `<boost/synchro/null_condition.hpp>`]
-[/==========================================================================================]
-
-[endsect]
-[/==========================================================================================]
-[section Header `<boost/synchro/null_mutex.hpp>`]
-[/==========================================================================================]
-
-[endsect]
-
-[/==========================================================================================]
-[section Header `<boost/synchro/null_synchronization_family.hpp>`]
-[/==========================================================================================]
-
-[section Class `null_synchronization_policy`]
-
-[*Synopsis]
-
-
- struct null_synchronization_policy
- {
- typedef boost::interprocess::null_mutex mutex_type;
- typedef boost::interprocess::null_mutex recursive_mutex_type;
- typedef boost::interprocess::null_mutex timed_mutex_type;
- typedef boost::interprocess::null_mutex recursive_timed_mutex_type;
- typedef boost::interprocess::null_mutex shared_mutex_type;
- typedef boost::condition_variable_any condition_variable_type;
- };
-
-[*Description]
-
-[*`nesteed_strict_locker` public types]
-
-# `;`
-
-[endsect]
-
-[endsect]
-[/==========================================================================================]
-[section Header `<boost/synchro/semaphore.hpp>`]
-[/==========================================================================================]
-
-[endsect]
-[/==========================================================================================]
-[section Header `<boost/synchro/process_synchronization_family.hpp>`]
-[/==========================================================================================]
-
-[section Class `process_synchronization_family`]
-
-[*Synopsis]
-
- struct process_synchronization_family
- {
- typedef boost::interprocess::interprocess_mutex mutex_type;
- typedef boost::interprocess::interprocess_recursive_mutex recursive_mutex_type;
- typedef boost::interprocess::interprocess_mutex timed_mutex_type;
- typedef boost::interprocess::interprocess_recursive_mutex recursive_timed_mutex_type;
- typedef boost::interprocess::interprocess_upgradable_mutex shared_mutex_type;
- typedef boost::interprocess::interprocess_condition condition_variable_type;
- };
-
-[*Description]
-
-[*`process_synchronization_family` public member types]
-
-# `;`
-
-[endsect]
-
-[endsect]
-[/==========================================================================================]
-[section Header `<boost/synchro/thread_synchronization_family.hpp>`]
-[/==========================================================================================]
-
-[section Class `thread_synchronization_family`]
-
-[*Synopsis]
-
- 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;
- };
-
-
-[endsect]
-
-
-
-
-[endsect]
-[/==========================================================================================]
-[section Header `<boost/synchro/locker/condition_locker.hpp>`]
-[/==========================================================================================]
-
- template <typename Condition> struct condition_backdoor;
- template <typename Condition> class condition_safe;
- template <typename Condition> class condition_safe_boosted;
- template <typename Lockable, typename Condition>
- class condition_unique_locker
- template <typename Lockable, typename Condition>
- class condition_shared_locker
-
- template <typename Lockable, typename Condition>
- class condition_unique_lockable
- template <typename Lockable, typename Condition>
- class condition_shared_lockable
-
-
-[section Template Class `condition_backdoor`]
-
- template <class Condition>
- struct condition_backdoor {
- condition_backdoor(condition_safe<Condition>&cnd);
- template <typename Locker>
- void wait(Locker& lock);
- template <typename Locker>
- bool wait_until(Locker& lock, boost::system_time const& abs_time);
- template<typename Locker, typename duration_type>
- bool wait_for(Locker& lock, duration_type const& rel_time);
-
- template <typename Locker, typename Predicate>
- void wait_when(Locker& lock, Predicate pred);
- template<typename Locker, typename predicate_type>
- bool wait_when_until(Locker& lock, predicate_type pred, boost::system_time const& abs_time);
- template<typename Locker, typename predicate_type, typename duration_type>
- bool wait_when_for(Locker& lock, predicate_type pred, duration_type const& rel_time);
-
- template <typename Locker>
- void notify_one(Locker& lock);
- template <typename Locker>
- void notify_all(Locker& lock);
- };
-
-[endsect]
-
-[section Template Class `condition_safe`]
-
- template <class Condition>
- class condition_safe {
- public:
- typedef Condition condition;
- typedef condition_backdoor<Condition> backdoor;
- void notify_one();
- void notify_all();
- };
-
-[endsect]
-
-[endsect]
-[/==========================================================================================]
-[section Header `<boost/synchro/locker/externally_locked.hpp>`]
-[/==========================================================================================]
-
-
-[section Template Class `externally_locked`]
-[*Synopsis]
-
- template <class T, class Lockable>
- class externally_locked {
- public:
- externally_locked(Lockable& owner);
- externally_locked(const T& obj, Lockable& own);
-
- template <typename Locker>
- T& get(Locker& locker);
- void set(const T& obj, Lockable& owner);
- };
-
-[*Description]
-`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<Lockable> object.
-
-[*`externally_locked` template parameters]
-
-* `T` : the type locked externally
-* `Lockable` : The lockable type used to synchronize the access to a T instance
-
-[*`externally_locked` public member functions]
-
-* `template <typename Locker> T& get(Locker& locker);`
-
-[*Requires:] mpl:and_<is_strict_locker<Locker>, is_same<lockable_type_trait<Locker>, Lockable>.
-
-[*Returns:] a reference to the type locked externally.
-
-[*Throws:] lock_error when the locker do not owns the lockable instance
-
-* `void set(const T& obj, Lockable& owner);`
-
-[*Effect:] reinit the type and lockable references with the given values.
-
-[*Example:]
-See
-
+[section Lockables]
+[/include concepts/Lockable.qbk]
+[include reference/lockable_traits.qbk]
+[include reference/lock_generator.qbk]
+[include reference/lockable_concept.qbk]
+[include reference/lockable_adapter.qbk]
[endsect]
+[section Condition Lockables]
+[/include concepts/ConditionLockables.qbk]
+[include reference/condition_safe.qbk]
+[include reference/condition_backdoor.qbk]
+[include reference/condition_lockable.qbk]
[endsect]
-[/==========================================================================================]
-[section Header `<boost/synchro/locker/is_strict_locker.hpp>`]
-[/==========================================================================================]
-
-
-[section Template Class `is_strict_locker`]
-
- template <typename Locker>
- struct is_strict_locker {
- typedef unspecified value;
- };
+[section Single-threaded]
+[include reference/null_mutex.qbk]
+[include reference/null_condition.qbk]
+[include reference/null_synchronization_family.qbk]
[endsect]
+[section Multi-threaded]
+[/include reference/thread/lockable_scope_traits.qbk]
+[/include reference/thread/mutex.qbk]
+[/include reference/thread/recursive_mutex.qbk]
+[/include reference/thread/shared_mutex.qbk]
+[/include reference/thread/locks.qbk]
+[include reference/thread_synchronization_family.qbk]
[endsect]
-[/==========================================================================================]
-[section Header `<boost/synchro/locker/locking_ptr.hpp>`]
-[/==========================================================================================]
-
-[section Class `locking_ptr`]
-
-[*Synopsis]
-
- template <typename T, typename Lockable>
- class locking_ptr : private boost::noncopyable {
- public:
- typedef T value_type;
- typedef Lockable mutex_type;
-
- locking_ptr(volatile value_type& obj, mutex_type& mtx);
- ~locking_ptr();
-
- value_type& operator*();
- const value_type& operator*() const;
- value_type* operator->();
- const value_type* operator->() const;
- };
-
-[*Description]
-
-The `locking_ptr` overloads `operator->` to return a temporary object that will
-perform the locking. This too provides an `operator->`. Calls to `operator->`
-are automatically chained by the compiler until a raw pointer type is returned. In
-pointer's `operator->` the lock is applied and in its destructor, called at the
-end of a full expression, it is released.
-
-[warning Programmers should be careful about attempting to access the same object twice in
-a statement using `locking_ptr`: this will cause deadlock if the synchronisation
-strategy is not re-entrant.]
-
-[*Example Code]
-
-
-[*`locking_ptr` constructors:destructors]
-# `locking_ptr(volatile value_type& obj, mutex_type& mtx);`
-# `~locking_ptr();`
-
-[*`locking_ptr` public member functions]
-
-# `value_type& operator*();`
-# `const value_type& operator*() const;`
-# `value_type* operator->();`
-# `const value_type* operator->() const;`
-
-[endsect]
-
-[section Class `sharable_locking_ptr`]
-[*Synopsis]
- template <typename T, typename SharableLockable>
- class sharable_locking_ptr
- : private boost::noncopyable {
- public:
- typedef T value_type;
- typedef SharableLockable mutex_type;
-
- sharable_locking_ptr(volatile value_type& obj, mutex_type& mtx);
- ~sharable_locking_ptr();
-
- value_type& operator*();
- const value_type& operator*() const;
- value_type* operator->();
- const value_type* operator->() const;
- };
-
- template <typename T, typename SharableLockable>
- class sharable_locking_ptr<const T, SharableLockable>
- : private boost::noncopyable {
- public:
- typedef T value_type;
- typedef SharableLockable mutex_type;
-
- sharable_locking_ptr(
- volatile const value_type& obj,
- mutex_type& mtx);
- ~sharable_locking_ptr();
-
- value_type& operator*();
- const value_type& operator*() const;
- value_type* operator->();
- const value_type* operator->() const;
- };
-
-[*Description]
-
-[*`nesteed_strict_locker` public member functions]
-
-# `;`
+[section Multi-process]
+[/include reference/process/lockable_scope_traits.qbk]
+[/include reference/process/mutex.qbk]
+[/include reference/process/recursive_mutex.qbk]
+[/include reference/process/upgradable_mutex.qbk]
+[/include reference/process/named_mutex.qbk]
+[/include reference/process/named_recursive_mutex.qbk]
+[/include reference/process/named_upgradable_mutex.qbk]
+[/include reference/process/locks.qbk]
+[include reference/process_synchronization_family.qbk]
[endsect]
-[endsect]
-[/==========================================================================================]
-[section Header `<boost/synchro/locker/on_derreference_locking_ptr.hpp>`]
-[/==========================================================================================]
-
-[section Class `on_derreference_locking_ptr`]
-[*Synopsis]
-
- template<typename T, typename Lockable>
- class on_derreference_locking_ptr
- {
- public:
- class pointer
- {
- public:
- explicit pointer(T* target, Lockable* mutex);
- ~pointer();
- T *operator->();
- };
-
- explicit on_derreference_locking_ptr(T &target, Lockable& mutex);
- pointer operator->() const;
- };
-
-[*Description]
-
-[*`nesteed_strict_locker` public member functions]
-
-# `;`
-
+[section Polymorphic Locks]
+[include reference/poly_lock.qbk]
+[include reference/lock_adapter.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 Header `<boost/synchro/locker/reverse_lock.hpp>`]
-[/==========================================================================================]
-
-[section Class `reverse_lock`]
-A reverse (or anti) lock.
-[*Synopsis]
-
-[*Description]
-
-This is an interesting adapter class that changes a lock into a reverse lock, i.e.,
-`lock` on this class calls `unlock` on the lockable, and `unlock` on this class
-calls `lock` on the lock. One motivation for this class is when we temporarily
-want to release a lock (which we have already acquired) but then reacquire it soon
-after.
-
-
-[*`reverse_lock` public member functions]
-
-# `;`
-# `;`
-# `;`
+[section Other]
+[include reference/semaphore.qbk]
[endsect]
+[section High Level]
+[include reference/monitor.qbk]
+[include reference/concurrent_component.qbk]
[endsect]
-[/==========================================================================================]
-[section Header `<boost/synchro/locker/strict_loker.hpp>`]
-[/==========================================================================================]
-
-[section Class `strict_locker`]
-[*Synopsis]
-
-[strict_locker_synopsis]
-[*Description]
-
-[Note strict_locker is not a model of Locable concept.]
-[*`strict_locker` template parameters]
-* `Lockable` : The exclusive lockable type used to synchronize exclusive access
-[*`strict_locker` public types]
-
-* `lockable_type` : The exclusive lockable type used to synchronize exclusive access
-* `lock_error` : The exception type throw incase of errors
-* `bool_type` : The bool_type of the safe_bool idiom
-
-[*`nesteed_strict_locker` public member functions]
-
-* `explicit strict_locker(lockable_type& obj);`
-* `~strict_locker();`
-* `operator bool_type() const;`
-* `bool operator!() const;`
-* `operator bool_type() const;`
-* `lockable_type* mutex() const;`
-* `lockable_type* get_lockable() const;`
-
-[*`nesteed_strict_locker` private and not defined member functions]
-
-* `strict_locker()`
-* `strict_locker(strict_locker&);`
-* `operator=(strict_locker&);`
-* `operator&();`
-* `void* operator new(std::size_t)`
-* `void* operator new[](std::size_t)`
-* `void operator delete(void*)`
-* `void operator delete[](void*)`
-
-[endsect]
-[section Class `nesteed_strict_locker`]
-[*Synopsis]
-
- template <typename Locker>
- class nesteed_strict_locker : private boost::noncopyable {
- public:
- typedef typename locker_traits<Locker>::bad_lock bad_lock;
-
- nesteed_strict_locker(Locker& lock);
- ~nesteed_strict_locker();
-
- typedef unspecified bool_type;
- operator bool_type() const;
-
- bool operator!() const
- bool owns_lock() const
- Mutex* mutex() const
- private:
- strict_locker();
- BOOST_NON_ALIAS(strict_locker);
- BOOST_NON_HEAP_ALLOCATED();
- };
-
-[*Description]
-
-[*`nesteed_strict_locker` public member functions]
-
-# `;`
-
-[endsect]
-
-[section Template Class `reverse_locker`]
-A reverse (or anti) locker.
-
-[*Synopsis]
-
-[*Description]
-`unlock` on construction and `lock` destruction.
-
-[*`reverse_locker` public member functions]
-
-# `;`
-# `;`
-# `;`
-
-[*Example Code]
-
-[endsect]
-
-
-[endsect]
-[/==========================================================================================]
-[section Header `<boost/synchro/conc/concurrent_component.hpp>`]
-[/==========================================================================================]
-
- class port;
- class object_port;
- template <typename TYPE> class qualified_port;
- class concurrent_component;
-
-[section Class `port`]
-
- class port {
- public:
- class synchronizer {
- synchronizer(port& that);
- ~synchronizer() {
- };
- port();
- ~port();
- void accept();
- bool accept_until(const boost::posix_time::ptime &abs_time);
- template<typename TimeDuration>
- bool accept_for(TimeDuration const& rel_time);
- };
-
-[endsect]
-
-[section Class `object_port`]
-
- class object_port {
- public:
- class synchronizer {
- synchronizer(object_port& that, const concurrent_component_base* snd);
- ~synchronizer() {
- };
- object_port();
- ~object_port();
- void accept(const void* snd);
- bool accept_until(const void* snd, const boost::posix_time::ptime &abs_time);
- template<typename TimeDuration>
- bool accept_for(const void* snd, TimeDuration const& rel_time);
- };
-
-[endsect]
-
-[section Class `qualified_port`]
-
- template <typename TYPE>
- class qualified_port {
- public:
- class synchronizer {
- synchronizer(qualified_port& that, const concurrent_component_base* snd);
- ~synchronizer() {
- };
- object_port();
- ~object_port();
- void accept(const TYPE* snd);
- bool accept_until(const TYPE* snd, const boost::posix_time::ptime &abs_time);
- template<typename TimeDuration>
- bool accept_for(const TYPE* snd, TimeDuration const& rel_time);
- };
-
-[endsect]
-
-[section Class `concurrent_component`]
-
- class concurrent_component
- typedef unspecified port;
- typedef unspecified object_port;
- typedef unspecified qualified_port;
-
- static void accept(port& p);
- static void accept_until(const boost::posix_time::ptime &abs_time, port& p);
- template<typename TimeDuration>
- static bool accept_for(TimeDuration const& rel_time, port& p);
-
- static void accept_all(port& p1, ..., port& pn);
- static void accept_all_until(const boost::posix_time::ptime &abs_time, port& p1, ..., port& pn);
- template<typename TimeDuration>
- static void accept_all_for(TimeDuration const& rel_time, port& p1, ..., port& pn);
-
- static void accept_any(port& p1, ..., port& pn);
- static void accept_any_until(const boost::posix_time::ptime &abs_time, port& p1, ..., port& pn);
- template<typename TimeDuration>
- static void accept_any_for(TimeDuration const& rel_time, port& p1, ..., port& pn);
-
- };
-
-[endsect]
-
-
-
-[endsect]
-[/==========================================================================================]
-[section Header `<boost/synchro/lockable_traits.hpp>`]
-[/==========================================================================================]
-
-namespace boost { namespace synchro {
-
- struct mono_threaded_tag;
- struct multi_threaded_tag;
- struct multi_process_tag;
- template <typename Lockable> struct scope_tag;
-
- template <typename Lockable> struct is_mono_threaded;
- template <typename Lockable> struct is_multi_threaded;
- template <typename Lockable> struct is_multi_process;
-
- struct process_lifetime_tag;
- struct kernel_lifetime_tag;
- struct filesystem_lifetime_tag;
- template <typename Lockable> struct lifetime_tag;
-
- struct anonymous_tag;
- struct named_tag;
- template <typename Lockable> struct naming_tag;
-
- struct exclusive_lock_tag;
- struct sharable_lock_tag;
- struct upgradable_lock_tag;
- template <typename Lockable> struct category_tag;
-
- template <typename Lockable> struct is_exclusive_lock;
- template <typename Lockable> struct is_sharable_lock;
- template <typename Lockable> struct is_upgradable_lock;
-
- struct non_recursive_tag;
- struct recursive_tag;
- template <typename Lockable> struct reentrancy_tag;
-
- template <typename Lockable> struct is_recursive_lock;
-
- struct hasnt_timed_interface_tag;
- struct has_timed_interface_tag;
- template <typename Lockable> struct timed_interface_tag;
-
- template <typename Lockable> struct has_timed_interface;
-
- template <typename Locker> struct lockable_type;
-
- template <typename Lockable> struct best_condition;
-
- template <typename Lockable> struct best_condition_any;
-
- template <typename Lockable> struct scoped_lock_type;
- template <typename Lockable> struct unique_lock_type;
- template <typename Lockable> struct shared_lock_type;
- template <typename Lockable> struct upgrade_lock_type;
-
- template <typename Lockable> struct lock_error_type;
-
- template <typename Lockable> struct move_object_type;
-
- template <typename Lockable> struct defer_lock_type;
- template <typename Lockable> struct adopt_lock_type;
- template <typename Lockable> struct try_to_lock_type;
-
- template<typename Scope> struct default_lifetime;
-
- template<
- typename Scope=multi_threaded_tag,
- typename Cathegory=exclusive_lock_tag,
- typename Reentrancy=non_recursive_tag,
- typename TimedInterface=has_timed_interface_tag,
- typename Lifetime=typename default_lifetime<Scope>,
- typename Naming=anonymous_tag,
- typename Base=void
- > struct lock_traits_base;
-
-
-
-[section Template Class `has_timed_interface`]
-
- template <typename Lockable>
- struct has_timed_interface
- : is_same_or_is_base_and_derived<
- has_timed_interface_tag,
- typename timed_interface_tag<Lockable>::type
- >
- {};
-
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `is_exclusive`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `is_shared`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `is_recursive`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `is_mono_threaded`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `is_multi_threaded`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `is_multi_process`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `mutex_type`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `scoped_lock`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `unique_lock`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `shared_lock`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `upgrade_lock`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `lock_error`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `moved_object`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `lock_error2`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `lock_error3`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-[section Class `lock_error4`]
-[*Synopsis]
-[*Description]
-
-[endsect]
-
-[section Class `lock_traits`]
-
-[*Synopsis]
-
- template<typename Lockable>
- struct lock_traits;
- typedef Lockable mutex_type;
- typedef unspecified scoped_lock;
- typedef unspecified unique_lock;
- typedef unspecified shared_lock;
- typedef unspecified upgrade_lock;
- typedef unspecified lock_error;
- typedef unspecified moved_object;
- static const unspecified defer_lock();
- static const unspecified adopt_lock();
- static const unspecified try_to_lock();
- };
-
-
-[*Description]
-Lock Traits characterise lockable types.
-
-[*`nesteed_strict_locker` public member types]
-
-# `;`
-
-[endsect]
-
-[endsect]
-
-
-
-
-
-
-
-
-[/==========================================================================================]
-[section 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.
-
-[*Synopsis]
-
-[*Description]
-
-This class, as locable, provide a set of general locking APIs.
-However, it defers our decision of what kind of lockable to use to the run time and delegates
-all locking operations to the actual lockable. Users must define a constructor in their subclass
-to initialize lock_.
-
-[*`adaptive_lock` public member functions]
-
-# `;`
-
-
-[endsect]
-
-[endsect]
[endsect]
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-14 07:43:05 EST (Sat, 14 Feb 2009)
@@ -5,9 +5,9 @@
/ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
/]
-[section:ext_references Appendix E: References]
+[section:ext_references References]
-[variablelist References
+[variablelist
[
[[@http://www.ddj.com/cpp/184403766
[*volatile - Multithreaded Programmer's Best Friend]]]
@@ -27,12 +27,11 @@
[Kevlin Henney]
]
-[/
- [[@http://www.comedi.org/projects/libpoet/boostbook/doc/boostbook/html/index.html
- [*libpoet]]]
- [Frank Mori Hess]
-/]
-
+[
+ [[@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.cs.wustl.edu/~schmidt/PDF/ACE-concurrency.pdf
[*An OO Encapsulation of Lightweight OS
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-14 07:43:05 EST (Sat, 14 Feb 2009)
@@ -51,7 +51,7 @@
[import ../../../boost/synchro/lockable_traits.hpp]
[import ../../../boost/synchro/lockable_concepts.hpp]
-[import ../../../boost/synchro/make_lockable.hpp]
+[import ../../../boost/synchro/lockable_adapter.hpp]
[import ../../../boost/synchro/monitor.hpp]
[import ../../../boost/synchro/thread_synchronization_family.hpp]
@@ -82,14 +82,10 @@
[include overview.qbk]
-[thread_synchronization_family]
-
[include users_guide.qbk]
-
[include reference.qbk]
-
[/xinclude autodoc.xml]
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-14 07:43:05 EST (Sat, 14 Feb 2009)
@@ -9,25 +9,14 @@
[section:tutorial Tutorial]
-Concurrent components may interact in different ways: they may access
-the same shared objects by, for example, executing functions of
-these objects; or they may communicate directly by executing functions of
-each other.
-
-Concurrent execution of objects requires a mechanism
-for synchronizing the access to shared objects, just as direct communication
-between objects may require synchronization. The basic mechanism for synchronization in
-Boost.Threads and Boost.Interprocess are the well known mutex and condition_variables. Mutexes
-and condition variables are, however, only useful for very simple synchronization problems.
-The Synchro Library therefore introduce high-level abstractions for handling more complicated
-synchronization problems, including monitor for guaranteeing exclusive access to an object,
-controling external locking and last a so-called rendezvous mechanism for handling direct communication between
-objects. All the concurrency abstractions being introduced are defined by means of mutexes an conditions.
+Concurrent components may interact in different ways: they may access the same shared objects by, for example, executing functions of these objects; or they may communicate directly by executing functions of each other.
+
+Concurrent execution of objects requires a mechanism for synchronizing the access to shared objects, just as direct communication between objects may require synchronization. The basic mechanism for synchronization in Boost.Threads and Boost.Interprocess are the well known mutex and condition_variables. Mutexes and condition variables are, however, only useful for very simple synchronization problems. The Synchro Library therefore introduce high-level abstractions for handling more complicated synchronization problems, including monitor for guaranteeing exclusive access to an object, controlling external locking and last a so-called rendezvous mechanism for handling direct communication between objects. All the concurrency abstractions being introduced are defined by means of mutexes an conditions.
[include tutorial/lockable.qbk]
[include tutorial/internal_locking.qbk]
-[incude volatile_locking_ptr.qbk]
+[include tutorial/volatile_locking_ptr.qbk]
[include tutorial/external_locking.qbk]
[include tutorial/rendezvous.qbk]
@@ -71,7 +60,7 @@
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 idion (Boost.Thread lock_guard) we can simplify a lot this using the scoped locks.
+With the RAII idiom (Boost.Thread lock_guard) we can simplify a lot this using the scoped locks.
[BankAccount_raii]
@@ -86,20 +75,20 @@
[monitor]
-Depending on the lock cathegory we have exclusive monitors and shared monitors
+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 make_exclusive_lockable provide to all the derived classes all ExclusiveLockable operations.
+Protected inheritance from exclusive_lockable_adapter provide to all the derived classes all ExclusiveLockable operations.
In addition has a protected nested class,
-synchroronizer, used when defining the monitor operations to synchronize the access critical regions.
+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 synchroronized operation means an operation
-using the synchroronizer guard defined within some monitor. Monitor is one example of a high-level concurrency abstraction
+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.
@@ -120,7 +109,7 @@
[sync_buffer_boost_thread_style]
-The Monitor class replace the nested synchroronizer unique_lock with the class `condition_locker`
+The Monitor class replace the nested synchronizer unique_lock with the class `condition_locker`
for this purpose:
[condition_locker]
@@ -150,11 +139,11 @@
to the Boost library.
-[section Internall locking]
+[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 multithreaded programming). In the code below, guard's
+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]
@@ -208,9 +197,9 @@
or inheriting from a class which add these lockable functions.
-The `make_exclusive_lockable` class
+The `exclusive_lockable_adapter` class
-[make_exclusive_lockable]
+[exclusive_lockable_adapter]
The `BankAccount` class result now in
@@ -241,7 +230,7 @@
is responsible for locking BankAccount properly.
class BankAccount
- : public make_exclusive_lockable<boost:mutex> {
+ : public exclusive_lockable_adapter<boost:mutex> {
int balance_;
public:
void Deposit(int amount) {
@@ -258,7 +247,7 @@
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 multithreaded class you settle on internal locking, you expose yourself to
+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.
@@ -396,7 +385,7 @@
A little code is worth 1,000 words, a (hacked into) saying goes, so here's the new BankAccount class:
class BankAccount
- : public make_exclusive_lockable<boost:recursive_mutex>
+ : public exclusive_lockable_adapter<boost:recursive_mutex>
{
int balance_;
public:
@@ -435,7 +424,7 @@
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 templated approach, having a
+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
@@ -487,7 +476,7 @@
Second, BankAccount needs to compare the locked object against this:
class BankAccount {
- : public make_exclusive_lockable<boost::recursive_mutex>
+ : public exclusive_lockable_adapter<boost::recursive_mutex>
int balance_;
public:
void Deposit(int amount, strict_lock<BankAccount>& guard) {
@@ -525,7 +514,7 @@
Say we have an AccountManager class that holds and manipulates a BankAccount object:
class AccountManager
- : public make_exclusive_lockable<boost::mutex>
+ : public exclusive_lockable_adapter<boost::mutex>
{
BankAccount checkingAcct_;
BankAccount savingsAcct_;
@@ -585,10 +574,10 @@
to duplicate every function taking a `strict\_lock`
or make these function templates functions.
The problem with template functions is that we don't profit anymore of
-the C++ type system. We must add some static metafunction that check that the Locker parameter is a strict locker.
-The problem is that we can not really check this or can we?. The `is_strict_locker` metafunction must be specialized by the strict locker
-developer. We need to belive it "sur parolle".
-The advantage is that now we can manage with more than two strict lockers without changing our code. Ths is really nice.
+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.
@@ -641,14 +630,14 @@
In the following sections we will introduce abstractions for making it possible
to synchronize such communication.
-[section Synchrohronized communication between components]
+[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.
-Synchrohronized communication is described in a generic class concurrent_component.
+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 `synchroronizer` class for defining operations controlled by the
+`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:
@@ -664,7 +653,7 @@
e2 = r_.m(e1);
```
-Since `m` is a synchroronized port operation, the execution of M has to be accepted
+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:
@@ -673,22 +662,22 @@
port::accept(p);
```
-The communication is carried out when `S` is executing `r\_.m` and `R` is executing
+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);`
+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
+# 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
+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
+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
@@ -753,7 +742,7 @@
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 subpatterns
+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:
@@ -1026,8 +1015,7 @@
`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.
+`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 {
@@ -1051,10 +1039,7 @@
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:
+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:
@@ -1066,8 +1051,7 @@
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:
+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_);
@@ -1077,9 +1061,7 @@
}
}
-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:
+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
@@ -1090,10 +1072,7 @@
}
}
-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:
+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();
@@ -1103,10 +1082,7 @@
[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.
+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
{
@@ -1118,19 +1094,13 @@
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:
+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`:
+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
{
@@ -1143,25 +1113,13 @@
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`!
+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.
+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
{
@@ -1173,10 +1131,7 @@
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:
+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
{
@@ -1186,9 +1141,8 @@
[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]
+
+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]
Modified: sandbox/synchro/libs/synchro/doc/tutorial/external_locking.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/tutorial/external_locking.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/tutorial/external_locking.qbk 2009-02-14 07:43:05 EST (Sat, 14 Feb 2009)
@@ -14,46 +14,29 @@
to the Boost library.
[/
-[section Internall locking]
+[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 multithreaded programming). In the code below, guard's
-constructor locks the passed-in object this, and guard's destructor unlocks this.
+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.
+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 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.
+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:
@@ -72,9 +55,9 @@
or inheriting from a class which add these lockable functions.
-The `make_exclusive_lockable` class
+The `exclusive_lockable_adapter` class
-[make_exclusive_lockable]
+[exclusive_lockable_adapter]
The `BankAccount` class result now in
@@ -84,28 +67,19 @@
[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.
-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.
+* 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.
+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 make_exclusive_lockable<boost:mutex> {
+ : public exclusive_lockable_adapter<boost:mutex> {
int balance_;
public:
void Deposit(int amount) {
@@ -116,45 +90,28 @@
}
};
-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 multithreaded 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.
+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).
+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.
+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]
@@ -187,17 +144,14 @@
};
]
-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:
+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_locker<T>` only starting from a valid T object. Notice that there is no
- other way you can create a `strict_locker<T>`.
+* You can create a `strict_locker<T>` only starting from a valid T object. Notice that there is no other way you can create a `strict_locker<T>`.
BankAccount myAccount("John Doe", "123-45-6789");
strict_locerk<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:
+* 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_locker<BankAccount> Foo(); // compile-time error
extern void Bar(strict_locker<BankAccount>); // compile-time error
@@ -209,8 +163,7 @@
// ok, Bar takes a reference to strict_locker<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.
+* 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_locker<BankAccount>(myAcount); //error!
@@ -221,46 +174,34 @@
};
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.)
+(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`:
+* 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_locker<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).
+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.
+* 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_locker<T>` is a reasonably
-strong guarantee that
+All these rules were put in place with one purpose-enforcing that owning a `strict_locker<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:
+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_locker<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_locker<BankAccount>` object.
-* BankAccount avoids code bloating by having the internal locked functions forward to the external
- locked functions, which do the actual job.
+* 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_locker<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_locker<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 make_exclusive_lockable<boost:recursive_mutex>
+ : public exclusive_lockable_adapter<boost:recursive_mutex>
{
int balance_;
public:
@@ -282,10 +223,7 @@
}
};
-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_locker<BankAccount>` and
-then you call `Deposit(int, strict_locker<BankAccount>&)` and `Withdraw(int, strict_locker<BankAccount>&)`. For
-example, here's the `ATMWithdrawal` function implemented correctly:
+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_locker<BankAccount>` and then you call `Deposit(int, strict_locker<BankAccount>&)` and `Withdraw(int, strict_locker<BankAccount>&)`. For example, here's the `ATMWithdrawal` function implemented correctly:
void ATMWithdrawal(BankAccount& acct, int sum) {
strict_locker<BankAccount> guard(acct);
@@ -295,17 +233,9 @@
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 templated approach, having a
-`strict_locker<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_locker<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:
+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 templated approach, having a `strict_locker<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_locker<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");
@@ -314,33 +244,19 @@
acct.Withdraw(2, guard);
}
-This code compiles warning-free but obviously doesn't do the right thing-it locks one account and
-uses another.
+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_locker<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.
-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_locker<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_locker<T>::get_lockable` function returns
-a reference to the locked 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_locker<T>::get_lockable` function returns a reference to the locked object.
template <class T> class strict_locker {
... as before ...
@@ -351,7 +267,7 @@
Second, BankAccount needs to compare the locked object against this:
class BankAccount {
- : public make_exclusive_lockable<boost::recursive_mutex>
+ : public exclusive_lockable_adapter<boost::recursive_mutex>
int balance_;
public:
void Deposit(int amount, strict_locker<BankAccount>& guard) {
@@ -368,8 +284,8 @@
[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:
+
+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_;
@@ -382,76 +298,54 @@
}
};
-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.
+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 make_exclusive_lockable<boost::mutex>
+ : 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"?
+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.
+`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>`:
+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_locker<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_locker 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.
+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_locker<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_locker 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:
+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.
+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:
+
+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_locker` the time we are working with `savingsAcct_`
-and then restore the ownership on `unique_lock`.
+We need a way to transfer the ownership from the `unique_lock` to a `strict_locker` 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.
+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 metafunction 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` metafunction must be specialized by the strict locker
-developer. We need to belive it "sur parolle".
-The advantage is that now we can manage with more than two strict lockers without changing our code. Ths is really nice.
+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 metafunction 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` metafunction must be specialized by the strict locker developer. We need to belive it "sur parolle". The advantage is that now we can manage with more than two strict lockers without changing our code. Ths is really nice.
Now we need to state that both classes are `strict_locker`s.
@@ -465,22 +359,17 @@
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.
+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`.
+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.
+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`.
+The `AccountManager::AMoreComplicatedFunction` function needs only to replace the `strict_locker` by a `nested_strict_locker`.
[AMoreComplicatedChecking2Savings]
Modified: sandbox/synchro/libs/synchro/doc/tutorial/internal_locking.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/tutorial/internal_locking.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/tutorial/internal_locking.qbk 2009-02-14 07:43:05 EST (Sat, 14 Feb 2009)
@@ -10,9 +10,7 @@
[section Concurrent threads of execution]
-Consider, for example, modeling a bank account class that supports
-simultaneous deposits and withdrawals from multiple locations (arguably
-the "Hello, World" of multithreaded programming).
+Consider, for example, modeling a bank account class that supports simultaneous deposits and withdrawals from multiple locations (arguably the "Hello, World" of multithreaded programming).
From here a component is a model of the `Callable` concept.
@@ -20,69 +18,40 @@
boost::thread thread1(S);
-where `S` is a model of `Callable`. The meaning of this expression is that execution
-of `S()` will take place concurrently with the current thread of execution executing the expression.
+where `S` is a model of `Callable`. The meaning of this expression is that execution of `S()` will take place concurrently with the current thread of execution executing the expression.
-The following example includes a bank account of a person (Joe) and two
-components, one corresponding to a bank agent depositing money in Joe's
-account, and one representing Joe. Joe will only be withdrawing money from
-the account:
+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.
+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.
+Execution of the Deposit and Withdraw operations will no longer be able to make simultaneous access to balance.
+
+Mutex is a simple and basic mechanism for obtaining synchronization. In the above example it is relatively easy to be convinced that the synchronization works correctly (in the absence of exception). In a system with several concurrent objects and several shared objects, it may be difficult to describe synchronization by means of mutexes. Programs that make heavy use of mutexes may be difficult to read and write. Instead, we shall introduce a number of generic classes for handling more complicated forms of synchronization and communication.
-Mutex is a simple and basic mechanism for obtaining synchronization.
-In the above example it is relatively easy to be convinced that the
-synchronization works correctly (in the absence of exception). In a system with several concurrent objects
-and several shared objects, it may be difficult to describe synchronization by
-means of mutexes. Programs that make heavy use of mutexes may be
-difficult to read and write. Instead, we shall introduce a number of generic
-classes for handling more complicated forms of synchronization and communication.
-
-With the RAII idion we can simplify a lot this using the scoped locks. In the code
-below, guard's constructor locks the passed-in object this, and guard's destructor
-unlocks this.
+With the RAII idiom we can simplify a lot this using the scoped locks. In the code below, guard's constructor locks the passed-in object this, and guard's destructor unlocks this.
[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.
-
-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 object-level locking idiom doesn't cover the entire richness of a threading model. For example, the model above is quite deadlock-prone when you try to coordinate multi-object transactions. Nonetheless, object-level locking is useful in many cases, and in combination with other mechanisms can provide a satisfactory solution to many threaded access problems in object-oriented programs.
+
+The BankAccount class above uses internal locking. Basically, a class that uses internal locking guarantees that any concurrent calls to its public member functions don't corrupt an instance of that class. This is typically ensured by having each public member function acquire a lock on the object upon entry. This way, for any given object of that class, there can be only one member function call active at any moment, so the operations are nicely serialized.
+
+This approach is reasonably easy to implement and has an attractive simplicity. Unfortunately, "simple" might sometimes morph into "simplistic."
+
+Internal locking is insufficient for many real-world synchronization tasks. Imagine that you want to implement an ATM withdrawal transaction with the BankAccount class. The requirements are simple. The ATM transaction consists of two withdrawals-one for the actual money and one for the $2 commission. The two withdrawals must appear in strict sequence; that is, no other transaction can exist between them.
The obvious implementation is erratic:
[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.
+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:
@@ -101,9 +70,9 @@
or inheriting from a class which add these lockable functions.
-The `make_exclusive_lockable` class
+The `exclusive_lockable_adapter` class
-[make_exclusive_lockable]
+[exclusive_lockable_adapter]
The `BankAccount` class result now in
@@ -114,15 +83,10 @@
[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.
+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.
+* 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`.
@@ -133,62 +97,40 @@
[section Monitors]
-The use of `mutex` and `lockers`, as in `BankAccount`, is a common way of defining objects
-shared by two or more concurrent components. The make_exclusive_lockable class was a first step.
-We shall therefore introduce an abstraction that makes it easier to define such objects. The following class
-describes a so-called monitor pattern.
+The use of `mutex` and `lockers`, as in `BankAccount`, is a common way of defining objects shared by two or more concurrent components. The exclusive_lockable_adapter class was a first step. We shall therefore introduce an abstraction that makes it easier to define such objects. The following class describes a so-called monitor pattern.
[monitor_1st_Synopsis]
[/shared_monitor]
[/monitor]
-A monitor object behaves like a `ExclusiveLockable` object but only for the inheriting classes.
-Protected inheritance from make_exclusive_lockable provide to all the derived classes all ExclusiveLockable operations.
-In addition has a protected nested class,
-synchroronizer, used when defining the monitor operations to synchronize the access critical regions.
-The BankAccount may be described using Monitor in the following way:
+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 synchroronized operation means an operation
-using the synchroronizer guard defined within some monitor. Monitor is one example of a high-level concurrency abstraction
-that can be defined by means of mutexes.
+In the following, a monitor means some sub-class of monitor. A synchroronized 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
+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:
+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 synchroronizer unique_lock with the class `condition_locker`
-for this purpose:
+The Monitor class replace the nested synchronizer unique_lock with the class `condition_locker` for this purpose:
[condition_unique_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`.
+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.
+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]
Modified: sandbox/synchro/libs/synchro/doc/tutorial/lockable.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/tutorial/lockable.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/tutorial/lockable.qbk 2009-02-14 07:43:05 EST (Sat, 14 Feb 2009)
@@ -5,14 +5,12 @@
/ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
/]
-
-
[section Lockables]
The following is an adaptation of the article "C++ Threading - A Generic-Programming Approach" by Kevin Henney.
[*Lock substitutability]
-The Boost (C++0x) mutexes have associated a category which form a subtyping hierarchy:
+The Boost (C++0x) mutexes have associated a category which form a sub-typing hierarchy:
ExclusiveLock <- SharableLock <- UpgradableLock
exclusive_lock <- sharable_lock <- upgradable_lock
@@ -20,7 +18,7 @@
[category_tag_hierarchy]
-Locking behaviour can be further cathegorized as:
+Locking behavior can be further categorized as:
* Re-entrancy: recursive or not
@@ -34,7 +32,7 @@
[scope_tag_hierarchy]
-* Lifetime: The lifetime of a lock coudl be associated to the process, the kernel or the filesystem
+* Lifetime: The lifetime of a lock could be associated to the process, the kernel or the file-system
process_lifetime <- kernel_lifetime <- filesystem_lifetime
@@ -53,7 +51,7 @@
* A null mutex is substitutable for all others in a single-threaded environment
We can see these axes of variation expressed against some
-Boost synchronisation mechanisms (from now bip stands for boost::interprocess):
+Boost synchronization mechanisms (from now bip stands for boost::interprocess):
* boost::mutex: ExclusiveLock, non-recursive, has-not-timed-interface, multi-threaded
* boost::shared_mutex: UpgradableLock, non-recursive, has-timed-interface, multi-threaded
@@ -62,35 +60,22 @@
[*Lock traits]
-Generic programming (writing code which works with any data type meeting a set of requirements) has become
-the method of choice for providing reusable code.
-However, there are times in generic programming when "generic" just isn't good enough - sometimes the
-differences between types are too large for an efficient generic implementation. This is when the traits
-technique becomes important - by encapsulating those properties that need to be considered on a type by
-type basis inside a traits class, we can minimize the amount of code that has to differ from one type to
-another, and maximize the amount of generic code.
-
-Consider an example:
-
-Boost.Synchro follow the design of Boost.TypeTraits, and contains a set of very specific traits classes, each
-of which encapsulate a single trait from the Localble or Loker concepts; for example, is the lock
-recursive? Or does the lock have a timed interface?
-
-The Boost.Synchro traits classes share a unified design: each class inherits from a the type true_type if the type
-has the specified property and inherits from false_type otherwise. As we will show, these classes can be used
-in generic programming to determine the properties of a given lock type and introduce optimizations that are
-appropriate for that case.
-
-The type-traits library also contains a set of classes that perform a specific transformation on a type; for
-example, they can remove a top-level const or volatile qualifier from a type. Each class that performs a
-transformation defines a single typedef-member type that is the result of the transformation. All of the
-type-traits classes are defined inside namespace boost; for brevity, namespace-qualification is omitted in
-most of the code samples given.
+Generic programming (writing code which works with any data type meeting a set of requirements) has become the method of choice for providing reusable code.
+
+However, there are times in generic programming when "generic" just isn't good enough - sometimes the differences between types are too large for an efficient generic implementation. This is when the traits technique becomes important - by encapsulating those properties that need to be considered on a type by type basis inside a traits class, we can minimize the amount of code that has to differ from one type to another, and maximize the amount of generic code.
+
+Consider an example:
+
+Boost.Synchro follow the design of Boost.TypeTraits, and contains a set of very specific traits classes, each of which encapsulate a single trait from the Lockable or Locker concepts; for example, is the lock recursive? Or does the lock have a timed interface?
+
+The Boost.Synchro traits classes share a unified design: each class inherits from a the type true_type if the type has the specified property and inherits from false_type otherwise. As we will show, these classes can be used in generic programming to determine the properties of a given lock type and introduce optimizations that are appropriate for that case.
+
+The type-traits library also contains a set of classes that perform a specific transformation on a type; for example, they can remove a top-level const or volatile qualifier from a type. Each class that performs a transformation defines a single typedef-member type that is the result of the transformation. All of the type-traits classes are defined inside namespace boost; for brevity, namespace-qualification is omitted in most of the code samples given.
[*Implementation]
-Most of the implementation is fairly repetitive anyway, so here we will just give you a flavor for how some of the classes are implemented. See the reference section for the full details
+Most of the implementation is fairly repetitive anyway, so here we will just give you a flavor for how some of the classes are implemented. See the reference section for the full details.
A lockable implementer must specialize the scope_tag template class. By default the scope_tag forward to a nested type scope.
@@ -110,74 +95,64 @@
template<>
struct scope_tag<boost::mutex> {
- typedef multi_threaded_tag type;
+ typedef multi_threaded_tag type;
};
-So the user must include this file to make boost::mutex a model of Locakble for Boost.Synchro.
+So the user must include this file to make boost::mutex a model of Lockable for Boost.Synchro.
-For example the trait is_multi_threaded is defined as : If Lockable has a scope_tag that inherits from multi_threaded_tag then inherits from true_type,
-otherwise inherits from false_type.
+For example the trait is_multi_threaded is defined as : If Lockable has a scope_tag that inherits from multi_threaded_tag then inherits from true_type, otherwise inherits from false_type.
-[is_multi_threaded]
+[is_multi_threaded]
[*Finding the best lock]
Inverse traits can match a lockable type based on specific traits, for a given family of lock types.
-It is also possible to specify characteristics to perform a reverse lookup to find a primitive lock type, either by
-exact match or by substitutable match.
+It is also possible to specify characteristics to perform a reverse lookup to find a primitive lock type, either by exact match or by substitutable match.
find_best_lock<>::type == boost::mutex
find_best_lock<mono_threaded_tag>::type == bsync::null_mutex
find_best_lock<multi_threaded_tag>::type == bsync::thread_mutex
find_best_lock<multi_process_tag>::type == bsync::interprocess_mutex
-
+
The user can also find a lock using mpl constraints as follows
-
- typedef find_best_lock_between<Lockables,
+
+ typedef find_best_lock_between<Lockables,
mpl::and<is_multi_threaded<_>, is_recursive<_> > >::type best;
+
+[*Synchronization family]
-[*Synchrohronization familly]
+A class that will do internal locking can be parameterized by the type of synchronization familly needed to achieve the desired level of concurrency control. This could depends of the usage scope of this class, and this can be mono_threaded, multi_threaded, multi_process.
-A class that will do internal locking can be parameterized by the type of synchronization familly needed to achieve
-the desired level of concurrency control. This could depends of the usage scope of this class, and this can be
-mono_threaded, multi_threaded, multi_process.
-
-For example the thread_synchronization_familly can be used to instantiate a message_queue class in a multi_threaded
-environment, all public methods will be thread-safe, with the corresponding overhead that implies. In contrast, if a
-null_synchronization_policy class is used to instantiate message_queue, all public methods will not be thread-safe,
-and there will be no additional overhead. A synchronization family must define typedef as for example
+For example the thread_synchronization_family can be used to instantiate a message_queue class in a multi_threaded environment, all public methods will be thread-safe, with the corresponding overhead that implies. In contrast, if a null_synchronization_policy class is used to instantiate message_queue, all public methods will not be thread-safe, and there will be no additional overhead. A synchronization family must define typedef as for example
[thread_synchronization_family]
[*Lockable concept]
-For the main category clasification, the library provides concept classes that can be used with Boost.ConceptCheck.
-For example 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.
+For the main category clasification, the library provides concept classes that can be used with Boost.ConceptCheck. For example 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.
[LockableConcept]
The user can now check staticaly that the template parameter is a model of Locable as follows
#include "boost/synchro/lockable_concepts.hpp"
- template <typename Lockable>
+ template <typename Lockable>
class my_class {
- BOOST_CONCEPT_ASSERT((LockableConcept<Lockable>));
+ BOOST_CONCEPT_ASSERT((LockableConcept<Lockable>));
// ...
};
-The same can be done for TimedLockableConcept, ShareLockableConcept and UpgradeLockableConcept
+The same can be done for TimedLockableConcept, ShareLockableConcept and UpgradeLockableConcept
(See the reference section for more details).
[*Syntactic lock traits]
-The locks on Boost.Thread and Boost::Interprocess do not follow the same interface.
-Most of the differences can be handled through traits, but other are better handled by adapting the interface.
+The locks on Boost.Thread and Boost::Interprocess do not follow the same interface. Most of the differences can be handled through traits, but other are better handled by adapting the interface.
-The
-* The scoped locks live in a different namespace and some have different names with the same semantic.
+The
+* The scoped locks live in a different namespace and some have different names with the same semantic.
[lock_lockers_traits]
@@ -185,7 +160,7 @@
bsync::shared_lock_type<Lockable>::type lock(mutex_);
-* The exception thrown lives in a different name space and different names with the same semantic.
+* The exception thrown lives in a different name space and different names with the same semantic.
[lock_exception_traits]
@@ -197,14 +172,12 @@
// ...
}
-* The move semantics (&&) are expressed with a class named differently.
+* The move semantics (&&) are expressed with a class named differently.
[lock_movable_traits]
-* The scoped locks can be initialized with static const variables in order to overload the constructor for lock
-adoption, lock deferral or try to lock. Even if the name of these variables is the same, these variables live in
-different namespace.
+* The scoped locks can be initialized with static const variables in order to overload the constructor for lock adoption, lock deferral or try to lock. Even if the name of these variables is the same, these variables live in different namespace.
[lockers_init_traits]
Modified: sandbox/synchro/libs/synchro/doc/tutorial/rendezvous.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/tutorial/rendezvous.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/tutorial/rendezvous.qbk 2009-02-14 07:43:05 EST (Sat, 14 Feb 2009)
@@ -5,33 +5,19 @@
/ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
/]
-
-
[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:
+
+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 Synchrohronized 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.
-Synchrohronized 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 `synchroronizer` 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:
+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]
@@ -45,55 +31,40 @@
e2 = r_.m(e1);
```
-Since `m` is a synchroronized 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:
+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
+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);`
+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 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`.
+# 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.
+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`:
+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.
+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 `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 `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`.
+# 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]
@@ -104,21 +75,11 @@
[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.
+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.
+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:
@@ -127,25 +88,19 @@
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 subpatterns
-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 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-classes 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.
+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]
@@ -155,20 +110,13 @@
[*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
+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.
+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.
+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.
[QualifiedSingleBuf]
@@ -180,27 +128,11 @@
[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:
+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();
Modified: sandbox/synchro/libs/synchro/doc/tutorial/volatile_locking_ptr.qbk
==============================================================================
--- sandbox/synchro/libs/synchro/doc/tutorial/volatile_locking_ptr.qbk (original)
+++ sandbox/synchro/libs/synchro/doc/tutorial/volatile_locking_ptr.qbk 2009-02-14 07:43:05 EST (Sat, 14 Feb 2009)
@@ -1,8 +1,9 @@
[/
/ Copyright (c) 2008 Vicente J. Botet Escriba
/
- / Distributed under the Boost Software License, Version 1.0. (See accompanying
- / file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ / Distributed under the Boost Software License, Version 1.0.
+ / (See accompanying file LICENSE_1_0.txt or
+ / copy at http://www.boost.org/LICENSE_1_0.txt)
/]
@@ -10,18 +11,13 @@
[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.
+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.
+Although both C and C++ Standards are conspicuously silent when it comes to threads, they do make a little concession to multi-threading, 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.
+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 multi-threaded programs becomes impossible, or the compiler wastes vast optimization opportunities. An explanation is in order.
Consider the following code:
@@ -44,19 +40,7 @@
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:
+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
{
@@ -66,20 +50,12 @@
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.
+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.
+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
{
@@ -106,67 +82,34 @@
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:
+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).
+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.
+The simplest and the most often-used synchronization device in multi-threaded 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.)
+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.
+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 multi-threaded 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:
+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
{
@@ -176,11 +119,7 @@
...
};
-`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` is very simple. `locking_ptr` implements an unsophisticated smart pointer. It focuses
-solely on collecting a `const_cast` and a critical section.
+`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` 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 {
@@ -204,10 +143,7 @@
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:
+In spite of its simplicity, `locking_ptr` is a very useful aid in writing correct multi-threaded 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:
@@ -219,8 +155,7 @@
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:
+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_);
@@ -230,9 +165,7 @@
}
}
-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:
+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
@@ -243,10 +176,7 @@
}
}
-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:
+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();
@@ -256,10 +186,7 @@
[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.
+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
{
@@ -271,19 +198,13 @@
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:
+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`:
+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
{
@@ -296,25 +217,13 @@
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`!
+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.
+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 multi-threaded 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
{
@@ -326,10 +235,7 @@
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:
+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
{
@@ -339,9 +245,7 @@
[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]
+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]
@@ -356,7 +260,6 @@
[locking_ptr_lockable_value_type]
-
[endsect]
[endsect]
Modified: sandbox/synchro/libs/synchro/example/Master_Slave.cpp
==============================================================================
--- sandbox/synchro/libs/synchro/example/Master_Slave.cpp (original)
+++ sandbox/synchro/libs/synchro/example/Master_Slave.cpp 2009-02-14 07:43:05 EST (Sat, 14 Feb 2009)
@@ -29,7 +29,7 @@
unsigned sum_;
bool End;
public:
- Slave() : End(false) {}
+ Slave() : End(false) {}
void Clear() {
// std::cout << "Clear()" << std::endl;
port::synchronizer _(receive_);
@@ -48,7 +48,7 @@
void Interrupt() {
// std::cout << "Interrupt()" << std::endl;
port::synchronizer _(receive_);
- End=true;
+ End=true;
}
void operator()() {
while (!End) {
Modified: sandbox/synchro/libs/synchro/example/SingleBuf.cpp
==============================================================================
--- sandbox/synchro/libs/synchro/example/SingleBuf.cpp (original)
+++ sandbox/synchro/libs/synchro/example/SingleBuf.cpp 2009-02-14 07:43:05 EST (Sat, 14 Feb 2009)
@@ -27,7 +27,7 @@
char bufCh_;
bool End;
public:
- SingleBuf() : End(false) {}
+ SingleBuf() : End(false) {}
void Put(char ch) {
port::synchronizer _(PutPort_);
// std::cout << "void Put("<<ch<<")" << std::endl;
@@ -41,7 +41,7 @@
void Interrupt() {
port::synchronizer _(PutPort_);
// std::cout << "Interrupt()" << std::endl;
- End=true;
+ End=true;
}
void operator()() {
while (!End) {
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