Boost logo

Boost-Commit :

Subject: [Boost-commit] svn:boost r83085 - trunk/libs/thread/doc
From: vicente.botet_at_[hidden]
Date: 2013-02-22 12:25:22


Author: viboes
Date: 2013-02-22 12:25:20 EST (Fri, 22 Feb 2013)
New Revision: 83085
URL: http://svn.boost.org/trac/boost/changeset/83085

Log:
Thread: Added doc on call_once + synchronized_value + lockable_adapter.
Added:
   trunk/libs/thread/doc/lockable_adapter.qbk (contents, props changed)
   trunk/libs/thread/doc/synchronized_value.qbk (contents, props changed)
Text files modified:
   trunk/libs/thread/doc/changes.qbk | 5 +++++
   trunk/libs/thread/doc/compliance.qbk | 10 +++++-----
   trunk/libs/thread/doc/internal_locking.qbk | 29 ++++++++++++++++++++++++-----
   trunk/libs/thread/doc/once.qbk | 20 +++++++++-----------
   trunk/libs/thread/doc/sync_tutorial.qbk | 2 ++
   trunk/libs/thread/doc/thread_ref.qbk | 2 +-
   6 files changed, 46 insertions(+), 22 deletions(-)

Modified: trunk/libs/thread/doc/changes.qbk
==============================================================================
--- trunk/libs/thread/doc/changes.qbk (original)
+++ trunk/libs/thread/doc/changes.qbk 2013-02-22 12:25:20 EST (Fri, 22 Feb 2013)
@@ -41,6 +41,7 @@
 See BOOST_THREAD_PROVIDES_SIGNATURE_PACKAGED_TASK and BOOST_THREAD_DONT_PROVIDE_SIGNATURE_PACKAGED_TASK.
 
 * [@http://svn.boost.org/trac/boost/ticket/7282 #7282] C++11 compliance: Add packaged_task::make_ready_at_thread_exit function
+* [@http://svn.boost.org/trac/boost/ticket/7285 #7285] C++11 compliance: Allow to pass movable arguments for call_once
 
 * [@http://svn.boost.org/trac/boost/ticket/7412 #7412] C++11 compliance: Add async from movable callable and movable arguments
 Provided when BOOST_THREAD_PROVIDES_VARIADIC_THREAD and BOOST_THREAD_PROVIDES_SIGNATURE_PACKAGED_TASK are defined (Default value from Boost 1.55):
@@ -63,11 +64,13 @@
 
 [*Fixed Bugs:]
 
+* [@http://svn.boost.org/trac/boost/ticket/5752 #5752] boost::call_once() is unreliable on some platforms
 * [@http://svn.boost.org/trac/boost/ticket/7464 #7464] BOOST_TEST(n_alive == 1); fails due to race condition in a regression test tool.
 * [@http://svn.boost.org/trac/boost/ticket/7657 #7657] Serious performance and memory consumption hit if condition_variable methods condition notify_one or notify_all is used repeatedly.
 * [@http://svn.boost.org/trac/boost/ticket/7665 #7665] this_thread::sleep_for no longer uses steady_clock in thread.
 * [@http://svn.boost.org/trac/boost/ticket/7668 #7668] thread_group::join_all() should check whether its threads are joinable.
 * [@http://svn.boost.org/trac/boost/ticket/7669 #7669] thread_group::join_all() should catch resource_deadlock_would_occur.
+* [@http://svn.boost.org/trac/boost/ticket/7671 #7671] Error including boost/thread.hpp header on iOS.
 * [@http://svn.boost.org/trac/boost/ticket/7672 #7672] lockable_traits.hpp syntax error: "defined" token misspelled.
 * [@http://svn.boost.org/trac/boost/ticket/7798 #7798] boost::future set_wait_callback thread safety issues.
 * [@http://svn.boost.org/trac/boost/ticket/7808 #7808] Incorrect description of effects for this_thread::sleep_for and this_thread::sleep_until.
@@ -75,6 +78,8 @@
 * [@http://svn.boost.org/trac/boost/ticket/7874 #7874] compile warning: thread.hpp:342: warning: type attributes are honored only at type definition.
 * [@http://svn.boost.org/trac/boost/ticket/7875 #7875] BOOST_THREAD_THROW_IF_PRECONDITION_NOT_SATISFIED should not be enabled by default.
 * [@http://svn.boost.org/trac/boost/ticket/7882 #7882] wrong exception text from condition_variable::wait(unique_lock<mutex>&).
+* [@http://svn.boost.org/trac/boost/ticket/7890 #7890] thread::do_try_join_until() is missing a return type.
+
 
 
 [heading Version 3.1.0 - boost 1.52]

Modified: trunk/libs/thread/doc/compliance.qbk
==============================================================================
--- trunk/libs/thread/doc/compliance.qbk (original)
+++ trunk/libs/thread/doc/compliance.qbk 2013-02-22 12:25:20 EST (Fri, 22 Feb 2013)
@@ -12,7 +12,7 @@
 
 [table C++11 standard Conformance
     [[Section] [Description] [Status] [Comments] [Ticket]]
- [[30] [Thread support library] [Partial] [-] [-]]
+ [[30] [Thread support library] [Yes] [-] [-]]
     [[30.1] [General] [-] [-] [-]]
     [[30.2] [Requirements] [-] [-] [-]]
     [[30.2.1] [Template parameter names] [-] [-] [-]]
@@ -53,16 +53,16 @@
     [[30.4.2.2.3] [unique_lock modifiers] [Yes] [-] [-]]
     [[30.4.2.2.4] [unique_lock observers] [Yes] [] [-]]
     [[30.4.3] [Generic locking algorithms] [Partial] [variadic] [#6227]]
- [[30.4.4] [Call once] [Partial] [call_once] [#7285]]
+ [[30.4.4] [Call once] [Yes] [-] [-]]
     [[30.4.4.1] [Struct once_flag] [Yes] [-] [-]]
- [[30.4.4.2] [Function call_once] [Partial] [interface] [#7285]]
+ [[30.4.4.2] [Function call_once] [Yes] [-] [-]]
     [[30.5] [Condition variables] [Yes] [-] [-]]
     [[30.5.1] [Class condition_variable] [Yes] [-] [-]]
     [[30.5.2] [Class condition_variable_any] [Yes] [-] [-]]
- [[30.6] [Futures] [Partial] [noexcept] [#7279]]
+ [[30.6] [Futures] [Yes] [-] [-]]
     [[30.6.1] [Overview] [Partial] [-] [-]]
     [[30.6.2] [Error handling] [Yes] [-] [-]]
- [[30.6.3] [Class future_error] [Partial] [noexcept] [#7279]]
+ [[30.6.3] [Class future_error] [-] [-] [-]]
     [[30.6.4] [Shared state] [-] [-] [-]]
     [[30.6.5] [Class template promise] [Yes] [-] [-]]
     [[30.6.6] [Class template future] [Yes] [-] [-]]

Modified: trunk/libs/thread/doc/internal_locking.qbk
==============================================================================
--- trunk/libs/thread/doc/internal_locking.qbk (original)
+++ trunk/libs/thread/doc/internal_locking.qbk 2013-02-22 12:25:20 EST (Fri, 22 Feb 2013)
@@ -55,6 +55,9 @@
 
 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.
 
+[endsect]
+[section Internal locking]
+
 The above example works well as long as the bankAgent and Joe doesn't access JoesAccount at the same time. There is, however, no guarantee that this will not happen. We may use a mutex to guarantee exclusive access to each bank.
 
   class BankAccount {
@@ -105,6 +108,9 @@
 
 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."
@@ -203,6 +209,24 @@
       // ...
   };
 
+The caller-ensured locking approach is more flexible and the most efficient, but very dangerous. In an implementation using caller-ensured locking, BankAccount still holds a mutex, but its member functions don't manipulate it at all. Deposit and Withdraw are not thread-safe anymore. Instead, the client code is responsible for locking BankAccount properly.
+
+ class BankAccount
+ : public basic_lockable_adapter<boost:mutex> {
+ int balance_;
+ public:
+ void Deposit(int amount) {
+ balance_ += amount;
+ }
+ void Withdraw(int amount) {
+ balance_ -= amount;
+ }
+ };
+
+Obviously, the caller-ensured locking approach has a safety problem. BankAccount's implementation code is finite, and easy to reach and maintain, but there's an unbounded amount of client code that manipulates BankAccount objects. In designing applications, it's important to differentiate between requirements imposed on bounded code and unbounded code. If your class makes undue requirements on unbounded code, that's usually a sign that encapsulation is out the window.
+
+To conclude, if in designing a multi-threaded class you settle on internal locking, you expose yourself to inefficiency or deadlocks. On the other hand, if you rely on caller-provided locking, you make your class error-prone and difficult to use. Finally, external locking completely avoids the issue by leaving it all to the client code.
+
 
 [endsect]
 
@@ -440,11 +464,6 @@
 [endsect] [/Monitors]
 ]
 
-[section Synchronized variables]
-[/include synchronized_value.qbk]
-[endsect] [/Synchronized variables]
-
-
 [endsect] [/Internal Locking]
 
 

Added: trunk/libs/thread/doc/lockable_adapter.qbk
==============================================================================
--- (empty file)
+++ trunk/libs/thread/doc/lockable_adapter.qbk 2013-02-22 12:25:20 EST (Fri, 22 Feb 2013)
@@ -0,0 +1,117 @@
+[/
+ (C) Copyright 2008-2013 Vicente J. Botet Escriba
+ Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file LICENSE_1_0.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt).
+]
+
+[/==========================================================================================]
+[section:lockable_adapter_hpp Header `<boost/thread/lockable_adapter.hpp>`]
+[/==========================================================================================]
+
+ namespace boost {
+ template <typename Lockable> class basic_lockable_adapter;
+ template <typename Lockable> class lockable_adapter;
+ template <typename TimedLock> class timed_lockable_adapter;
+ template <typename SharableLock> class shared_lockable_adapter;
+ template <typename UpgradableLock> class upgrade_lockable_adapter;
+ }
+
+[section Template Class `basic_lockable_adapter<>`]
+
+ template <typename Lockable>
+ class basic_lockable_adapter {
+ public:
+ basic_lockable_adapter(basic_lockable_adapter const&) = delete
+ basic_lockable_adapter& opearator=(basic_lockable_adapter const&) = delete
+
+ typedef Lockable mutex_type;
+
+ basic_lockable_adapter() {}
+ void lock();
+ void unlock();
+ bool try_lock();
+ };
+
+[endsect]
+[section Template Class `lockable_adapter<>`]
+
+ template <typename Lockable>
+ class lockable_adapter : : public basic_lockable_adapter<Lockable> {
+ public:
+ lockable_adapter() {}
+
+ bool try_lock();
+ };
+
+[endsect]
+
+[section Template Class `timed_lockable_adapter<>`]
+
+ template <typename TimedLock>
+ class timed_lockable_adapter : public lockable_adapter<TimedLock> {
+ public:
+ timed_lockable_adapter() {}
+
+ bool try_lock_until(system_time const & abs_time);
+ template<typename TimeDuration>
+ bool try_lock_for(TimeDuration const & relative_time);
+
+ void lock_until(system_time const & abs_time);
+ template<typename TimeDuration>
+ void lock_for(TimeDuration const & relative_time);
+ };
+
+[endsect]
+[section Template Class `shared_lockable_adapter<>`]
+
+ template <typename SharableLock>
+ class shared_lockable_adapter : public timed_lockable_adapter<SharableLock> {
+ public:
+ shared_lockable_adapter() {}
+ void lock_shared();
+ bool try_lock_shared();
+ void unlock_shared();
+
+ bool try_lock_shared_until(system_time const& t);
+ template<typename TimeDuration>
+ bool try_lock_shared_for(TimeDuration const& t);
+
+ template<typename TimeDuration>
+ void lock_shared_for(TimeDuration const& t);
+ void lock_shared_until(system_time const& t);
+ };
+
+
+[endsect]
+[section Template Class `upgrade_lockable_adapter<>`]
+
+ template <typename UpgradableLock>
+ class upgrade_lockable_adapter : public shared_lockable_adapter<UpgradableLock>{
+ public:
+ upgrade_lockable_adapter();
+
+ void lock_upgrade();
+ bool try_lock_upgrade();
+ void unlock_upgrade();
+
+ void unlock_upgrade_and_lock();
+ void unlock_and_lock_upgrade();
+ void unlock_and_lock_shared();
+ void unlock_upgrade_and_lock_shared();
+
+ bool try_lock_upgrade_until(system_time const&t);
+ template<typename TimeDuration>
+ bool try_lock_upgrade_for(TimeDuration const&t);
+ void lock_upgrade_until(system_time const&t);
+ template<typename TimeDuration>
+ void lock_upgrade_for(TimeDuration const&t);
+ };
+
+
+
+[endsect]
+[endsect]
+
+
+

Modified: trunk/libs/thread/doc/once.qbk
==============================================================================
--- trunk/libs/thread/doc/once.qbk (original)
+++ trunk/libs/thread/doc/once.qbk 2013-02-22 12:25:20 EST (Fri, 22 Feb 2013)
@@ -12,8 +12,8 @@
   namespace boost
   {
     struct once_flag;
- template<typename Callable>
- void call_once(once_flag& flag,Callable func);
+ template<typename Function, class ...ArgTypes>
+ inline void call_once(once_flag& flag, Function&& f, ArgTypes&&... args);
 
   #if defined BOOST_THREAD_PROVIDES_DEPRECATED_FEATURES_SINCE_V3_0_0
     void call_once(void (*func)(),once_flag& flag);
@@ -45,24 +45,22 @@
 
 [section:call_once Non-member function `call_once`]
 
- template<typename Callable>
- void call_once(once_flag& flag,Callable func);
+ template<typename Function, class ...ArgTypes>
+ inline void call_once(once_flag& flag, Function&& f, ArgTypes&&... args);
 
 [variablelist
 
-[[Requires:] [`Callable` is `CopyConstructible`. Copying `func` shall have no side effects, and the effect of calling the copy shall
-be equivalent to calling the original. ]]
+[[Requires:] [`Function` and each or the `ArgTypes` are `MoveConstructible` and `invoke(decay_copy(boost::forward<Function>(f)), decay_copy(boost::forward<ArgTypes>(args))...)` shall be well formed. ]]
 
 [[Effects:] [Calls to `call_once` on the same `once_flag` object are serialized. If there has been no prior effective `call_once` on
-the same `once_flag` object, the argument `func` (or a copy thereof) is called as-if by invoking `func()`, and the invocation of
-`call_once` is effective if and only if `func()` returns without exception. If an exception is thrown, the exception is
-propagated to the caller. If there has been a prior effective `call_once` on the same `once_flag` object, the `call_once` returns
+the same `once_flag` object, the argument `func` is called as-if by invoking `invoke(decay_copy(boost::forward<Function>(f)), decay_copy(boost::forward<ArgTypes>(args))...)`, and the invocation of
+`call_once` is effective if and only if `invoke(decay_copy(boost::forward<Function>(f)), decay_copy(boost::forward<ArgTypes>(args))...)` returns without exception. If an exception is thrown, the exception is propagated to the caller. If there has been a prior effective `call_once` on the same `once_flag` object, the `call_once` returns
 without invoking `func`. ]]
 
 [[Synchronization:] [The completion of an effective `call_once` invocation on a `once_flag` object, synchronizes with
 all subsequent `call_once` invocations on the same `once_flag` object. ]]
 
-[[Throws:] [`thread_resource_error` when the effects cannot be achieved. or any exception propagated from `func`.]]
+[[Throws:] [`thread_resource_error` when the effects cannot be achieved or any exception propagated from `func`.]]
 
 [[Note:] [The function passed to `call_once` must not also call
 `call_once` passing the same `once_flag` object. This may cause
@@ -77,7 +75,7 @@
 
     void call_once(void (*func)(),once_flag& flag);
     
-This second overload is provided for backwards compatibility. The effects of `call_once(func,flag)` shall be the same as those of
+This second overload is provided for backwards compatibility and is deprecated. The effects of `call_once(func,flag)` shall be the same as those of
 `call_once(flag,func)`.
 
 [endsect]

Modified: trunk/libs/thread/doc/sync_tutorial.qbk
==============================================================================
--- trunk/libs/thread/doc/sync_tutorial.qbk (original)
+++ trunk/libs/thread/doc/sync_tutorial.qbk 2013-02-22 12:25:20 EST (Fri, 22 Feb 2013)
@@ -18,6 +18,8 @@
 
 [include external_locking.qbk]
 
+[include synchronized_value.qbk]
+
 [section:with Executing Around a Function]
 
 In particular, the library provides some lock factories.

Added: trunk/libs/thread/doc/synchronized_value.qbk
==============================================================================
--- (empty file)
+++ trunk/libs/thread/doc/synchronized_value.qbk 2013-02-22 12:25:20 EST (Fri, 22 Feb 2013)
@@ -0,0 +1,125 @@
+[/
+ / Copyright (c) 2013 Vicente J. Botet Escriba
+ /
+ / Distributed under the Boost Software License, Version 1.0. (See accompanying
+ / file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ /]
+
+
+[section Synchronized values]
+[note This tutorial is an adaptation of the paper of Anthony Williams "Enforcing Correct Mutex Usage with Synchronized Values" to the Boost library.]
+
+[section The Problem with Mutexes]
+
+The key problem with protecting shared data with a mutex is that there is no easy way to associate the mutex with the data. It is thus relatively easy to accidentally write code that fails to lock the right mutex - or even locks the wrong mutex - and the compiler will not help you.
+
+
+ std::mutex m1;
+ int value1;
+ std::mutex m2;
+ int value2;
+
+ int readValue1()
+ {
+ boost::lock_guard<boost::mutex> lk(m1);
+ return value1;
+ }
+ int readValue2()
+ {
+ boost::lock_guard<boost::mutex> lk(m1); // oops: wrong mutex
+ return value2;
+ }
+
+Moreover, managing the mutex lock also clutters the source code, making it harder to see what is really going on.
+
+The use of synchronized_value solves both these problems - the mutex is intimately tied to the value, so you cannot access it without a lock, and yet access semantics are still straightforward. For simple accesses, synchronized_value behaves like a pointer-to-T; for example:
+
+
+ boost::synchronized_value<std::string> value3;
+ std::string readValue3()
+ {
+ return *value3;
+ }
+ void setValue3(std::string const& newVal)
+ {
+ *value3=newVal;
+ }
+ void appendToValue3(std::string const& extra)
+ {
+ value3->append(extra);
+ }
+
+Both forms of pointer dereference return a proxy object rather than a real reference, to ensure that the lock on the mutex is held across the assignment or method call, but this is transparent to the user.
+
+[endsect] [/The Problem with Mutexes]
+
+[section Beyond Simple Accesses]
+
+The pointer-like semantics work very well for simple accesses such as assignment and calls to member functions. However, sometimes you need to perform an operation that requires multiple accesses under protection of the same lock, and that's what the synchronize() method provides.
+
+By calling synchronize() you obtain an strict_lock_ptr object that holds a lock on the mutex protecting the data, and which can be used to access the protected data. The lock is held until the strict_lock_ptr object is destroyed, so you can safely perform multi-part operations. The strict_lock_ptr object also acts as a pointer-to-T, just like synchronized_value does, but this time the lock is already held. For example, the following function adds a trailing slash to a path held in a synchronized_value. The use of the strict_lock_ptr object ensures that the string hasn't changed in between the query and the update.
+
+ void addTrailingSlashIfMissing(boost::synchronized_value<std::string> & path)
+ {
+ boost::strict_lock_ptr<std::string> u=path.synchronize();
+
+ if(u->empty() || (*u->rbegin()!='/'))
+ {
+ *u+='/';
+ }
+ }
+
+[endsect] [/Beyond Simple Accesses]
+
+[section Operations Across Multiple Objects]
+
+Though synchronized_value works very well for protecting a single object of type T, nothing that we've seen so far solves the problem of operations that require atomic access to multiple objects unless those objects can be combined within a single structure protected by a single mutex.
+
+One way to protect access to two synchronized_value objects is to construct a strict_lock_ptr for each object and use those to access the respective protected values; for instance:
+
+ synchronized_value<std::queue<MessageType> > q1,q2;
+ void transferMessage()
+ {
+ strict_lock_ptr<std::queue<MessageType> > u1 = q1.synchronize();
+ strict_lock_ptr<std::queue<MessageType> > u2 = q2.synchronize();
+
+ if(!u1->empty())
+ {
+ u2->push_back(u1->front());
+ u1->pop_front();
+ }
+ }
+
+This works well in some scenarios, but not all -- if the same two objects are updated together in different sections of code then you need to take care to ensure that the strict_lock_ptr objects are constructed in the same sequence in all cases, otherwise you have the potential for deadlock. This is just the same as when acquiring any two mutexes.
+
+In order to be able to use the dead-lock free lock algorithms we need to use instead unique_lock_ptr, which is Lockable.
+
+ synchronized_value<std::queue<MessageType> > q1,q2;
+ void transferMessage()
+ {
+ unique_lock_ptr<std::queue<MessageType> > u1 = q1.unique_synchronize(boost::defer_lock);
+ unique_lock_ptr<std::queue<MessageType> > u2 = q2.unique_synchronize(boost::defer_lock);
+ boost::lock(u1,u2); // dead-lock free algorithm
+
+ if(!u1->empty())
+ {
+ u2->push_back(u1->front());
+ u1->pop_front();
+ }
+ }
+
+
+[endsect] [/Operations Across Multiple Objects]
+
+[section Value semantics]
+
+synchronized_value has value semantics even if the syntax let it appear as a pointer.
+
+[endsect] [/Value semantics]
+
+
+[endsect] [/Synchronized variables]
+
+
+
+

Modified: trunk/libs/thread/doc/thread_ref.qbk
==============================================================================
--- trunk/libs/thread/doc/thread_ref.qbk (original)
+++ trunk/libs/thread/doc/thread_ref.qbk 2013-02-22 12:25:20 EST (Fri, 22 Feb 2013)
@@ -201,7 +201,7 @@
 
 [endsect]
 
-[section:detac Detaching thread]
+[section:detach Detaching thread]
 
 A thread can be detached by explicitly invoking the __detach__ member function on the __thread__
 object. In this case, the __thread__ object ceases to represent the now-detached thread, and instead represents __not_a_thread__.


Boost-Commit list run by bdawes at acm.org, david.abrahams at rcn.com, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk