Document Number: | N3197=10-0187 |
Date: | 2010-11-12 |
Author: | Anthony
Williams Just Software Solutions Ltd |
This paper revises N3130.
It provides a proposed resolution
for LWG
issue 1268, and comments GB 138, CH 26, CH27 and US 188 on the
FCD (see N3102). The basic premise of LWG issue 1268 and GB 138 is
that the "Mutex requirements" from the current working draft are
worded as if they are requirements on all lockable types,
including user-defined mutexes and instantiations
of unique_lock
. However, the requirements really only
need apply to the standard mutex types such
as std::mutex
, and are too strong when applied to
user-defined mutex types. Also, the requirements are worded in
terms of threads, when they could apply equally for other
asynchronous agents.
This paper therefore proposes to separate the existing requirements on the standard mutex types from the general requirements on all lockable types. Note to editor: this paper should be consistent with N3128 under the assumption that they would both be accepted; any inconsistencies are accidental and unintended.
Add a new section to 30.2 [thread.req] after 30.2.4 [thread.req.timing] as follows:
30.2.5 Requirements for Lockable types
An execution agent is an entity, such as a thread, that may perform work in parallel with other execution agents. [Note: Implementations or users may introduce other kinds of agents, such as processes or thread-pool tasks. —end note] The calling agent is determined by context, e.g. the calling thread that contains the call, etc.
[Note: Some lockable objects are “agent-oblivious” in that they work for any execution-agent model because they do not determine or store the agent’s ID (e.g., an ordinary spin-lock). —end note]
The standard library templates
unique_lock
(30.4.3.2 [thread.lock.unique]),lock_guard
(30.4.3.1 [thread.lock.guard]),lock
,try_lock
(30.4.4 [thread.lock.algorithm]) andcondition_variable_any
(30.5.2 [thread.condition.condvarany]) all operate on user-supplied lockable objects. Such an object must support the member functions specified for either theBasicLockable
requirements, theLockable
requirements or theTimedLockable
requirements as appropriate to acquire or release ownership of alock
by a given execution agent. [Note: the nature of any lock ownership and any synchronization it may entail are not part of these requirements. — end note]30.2.5.1 BasicLockable Requirements
In order for a type
L
to qualify as aBasicLockable
type, the following expressions must be supported, with the specified semantics, wherem
denotes a value of typeL
:The expression
m.lock()
shall be well-formed and have the following semantics:
- Effects:
- Block until a lock can be acquired for the current execution agent. If an exception is thrown then a lock shall not have been acquired for the current execution agent.
The expression
m.unlock()
shall be well-formed and have the following semantics:
- Requires:
- The current execution agent shall hold a lock on
m
.- Effects:
- Release a lock on
m
held by the current execution agent.- Throws:
- Nothing.
30.2.5.2 Lockable Requirements
In order for a type
L
to qualify as aLockable
type, it must meet theBasicLockable
requirements. In addition, the following expressions must be supported, with the specified semantics, wherem
denotes a value of typeL
:The expression
m.try_lock()
shall be well-formed and have the following semantics:
- Effects:
- Attempt to acquire a lock for the current execution agent without blocking. If an exception is thrown then a lock shall not have been acquired for the current execution agent.
- Return type:
bool
- Returns:
true
if the lock was acquired,false
otherwise.30.2.5.3
TimedLockable
RequirementsFor a type
TL
to qualify asTimedLockable
it must meet theLockable
requirements, and additionally the following expressions must be well-formed, with the specified semantics, wherem
is an instance of a typeTL
,rel_time
denotes instantiation ofduration
(20.10.3 [time.duration]) andabs_time
denotes an instantiation oftime_point
(20.10.4 [time.point])The expression
m.try_lock_for(rel_time)
shall be well-formed and have the following semantics:
- Effects:
- Attempt to acquire a lock for the current execution agent within the relative timeout (30.2.4 [thread.req.timing]) specified by
rel_time
. The function shall return within the timeout specified byrel_time
only if it has obtained a lock onm
for the current execution agent. If an exception is thrown then a lock shall not have been acquired for the current execution agent.- Return type:
bool
- Returns:
true
if the lock was acquired,false
otherwise.The expression
m.try_lock_until(abs_time)
shall be well-formed and have the following semantics:
- Effects:
- Attempt to acquire a lock for the current execution agent before the absolute timeout (30.2.4 [thread.req.timing]) specified by
abs_time
. The function shall return before the timeout specified byabs_time
only if it has obtained a lock onm
for the current execution agent. If an exception is thrown then a lock shall not have been acquired for the current execution agent.- Return type:
bool
- Returns:
true
if the lock was acquired,false
otherwise.
Replace 30.4.1 [thread.mutex.requirements] paragraph 1 with the following:
1 A mutex object facilitates protection against data races and allows
thread-safe synchronization of data betweenthreadsexecution agents (30.2.5). Athreadexecution agent owns a mutex from the time it successfully calls one of the lock functions until it calls unlock. Mutexes may be either recursive or non-recursive, and may grant simultaneous ownership to one or manythreadsexecution agents. The mutex types supplied by the standard library provide exclusive ownership semantics for threads: only one thread may own the mutex at a time. Both recursive and non-recursive mutexes are supplied.
Replace 30.4.1 [thread.mutex.requirements] paragraph 2 with the following:
2 This section describes requirements on
template argument types used to instantiate templates defined inthe mutex types supplied by the C++ standard library.The template definitions in the C++ standard library referThese types shall conform to thenamedrequirements whose details are set out below. In this description,Mutex
m
is an object ofaone of the standard library mutex typesMutex
typestd::mutex
,std::recursive_mutex
,std::timed_mutex
orstd::recursive_timed_mutex
..
Add the following paragraph after 30.4.1 [thread.mutex.requirements] paragraph 2:
A mutex type shall conform to the
Lockable
requirements (30.2.5.2).
Insert a new paragraph before 30.4.1 [thread.mutex.requirements] paragraph 7:
Requires: If
m
is of typestd::mutex
orstd::timed_mutex
then the calling thread does not own the mutex.
Insert a new paragraph before 30.4.1 [thread.mutex.requirements] paragraph 14:
Requires: If
m
is of typestd::mutex
orstd::timed_mutex
then the calling thread does not own the mutex.
Replace 30.4.2 [thread.timedmutex.requirements] paragraph 1 with the following:
In addition to the requirements set out in [thread.mutex.requirements] (30.4.1), the C++ standard library types
std::timed_mutex
andstd::recursive_timed_mutex
(the timed mutex types)Ashall meet the requirements set outTimedMutex
type shall meet the requirements for aMutex
type. In addition, itin this Clause 30.4.2below, whererel_time
denotes an instantiation ofduration
(20.10.3 [time.duration]) andabs_time
denotes an instantiation oftime_point
(20.10.4 [time.point]).
Add the following paragraph after 30.4.2 [thread.timedmutex.requirements] paragraph 1:
A timed mutex type shall conform to the
TimedLockable
requirements (30.2.5.3).
Modify 30.4.2 [thread.mutex.requirements] paragraph 3 as follows:
Requires: If the tick period of
rel_time
is not exactly convertible to the native tick period, the duration shall be rounded up to the nearest native tick period. Ifm
is of typestd::timed_mutex
then the calling thread does not own the mutex.
Insert a new paragraph before 30.4.2 [thread.mutex.requirements] paragraph 10 as follows:
Requires: If
m
is of typestd::timed_mutex
then the calling thread does not own the mutex.
Add the following paragraph following 30.4.3.1 [thread.lock.guard] paragraph 1:
The supplied
Mutex
type shall meet theLockable
requirements (30.2.5.2).
Add the following paragraph following 30.4.3.2 [thread.lock.unique] paragraph 1:
The supplied
Mutex
type shall meet theLockable
requirements (30.2.5.2).unique_lock<Mutex>
meets theLockable
requirements. IfMutex
meets theTimedLockable
requirements (30.2.5.3) thenunique_lock<Mutex>
also meets theTimedLockable
requirements.
Replace the use of "mutex" or "mutex object" with "lockable object" throughout clause 30.4.3. 30.4.3 [thread.mutex.locks] paragraph 1:
1 A lock is an object that holds a reference to a
mutexlockable object and may unlock themutexlockable object during the lock’s destruction (such as when leaving block scope). Athread of executionexecution agent may use a lock to aid in managingmutexownership of a lockable object in an exception safe manner. A lock is said to own amutexlockable object if it is currently managing the ownership of thatmutexlockable object for athread of executionexecution agent. A lock does not manage the lifetime of themutexlockable object it references. [ Note: Locks are intended to ease the burden of unlocking themutexlockable object under both normal and exceptional circumstances. — end note ]
30.4.3 [thread.lock] paragraph 2:
2 Some lock constructors take tag types which describe what should be done with the
mutexlockable object during the lock’s constuction.
30.4.3.1 [thread.lock.guard] paragraph 1:
1 An object of type
lock_guard
controls the ownership of amutexlockable object within a scope. Alock_guard
object maintains ownership of amutexlockable object throughout thelock_guard
object’s lifetime. The behavior of a program is undefined if themutexlockable object referenced bypm
does not exist for the entire lifetime (3.8) of thelock_guard
object.Mutex
shall meet theBasicLockable
requirements (30.2.5.2).
30.4.3.2 [thread.lock.unique] paragraph 1:
1 An object of type
unique_lock
controls the ownership of amutexlockable object within a scope.MutexoOwnership of the lockable object may be acquired at construction or after construction, and may be transferred, after acquisition, to anotherunique_lock
object. Objects of typeunique_lock
are not copyable but are movable. The behavior of a program is undefined if the contained pointerpm
is not null and the mutex pointed to bypm
does not exist for the entire remaining lifetime (3.8) of theunique_lock
object.Mutex
shall meet theLockable
requirements (30.2.5.2).
Add the following to the precondition of unique_lock(mutex_type&
m,
const chrono::time_point<Clock, Duration>& abs_time)
in
30.4.3.2.1 [thread.lock.unique.cons] paragraph 18:
template <class Clock, class Duration> unique_lock(mutex_type& m, const chrono::time_point<Clock, Duration>& abs_time);18 Requires: If
mutex_type
is not a recursive mutex the calling thread does not own the mutex. The suppliedmutex_type
type shall meet theTimedLockable
requirements (30.2.5.3).
Add the following to the precondition of unique_lock(mutex_type&
m,
const chrono::duration<Rep, Period>& rel_time)
in
30.4.3.2.1 [thread.lock.unique.cons]
paragraph 22
22 Requires: If
mutex_type
is not a recursive mutex the calling thread does not own the mutex. The suppliedmutex_type
type shall meet theTimedLockable
requirements (30.2.5.3).
Add the following as a precondition of bool try_lock_until(const
chrono::time_point<Clock, Duration>& abs_time)
before
30.4.3.2.2 [thread.lock.unique.locking] paragraph 10
template <class Clock, class Duration> bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);Requires: The supplied
mutex_type
type shall meet theTimedLockable
requirements (30.2.5.3).
Add the following as a precondition of bool try_lock_for(const
chrono::duration<Rep, Period>& rel_time)
before
30.4.3.2.2 [thread.lock.unique.locking] paragraph 15
template <class Rep, class Period> bool try_lock_for(const chrono::duration<Rep, Period>& rel_time);Requires: The supplied
mutex_type
type shall meet theTimedLockable
requirements (30.2.5.3).
Replace 30.4.4 [thread.lock.algorithm] p1 with the following:
template <class L1, class L2, class... L3> int try_lock(L1&, L2&, L3&...);1 Requires: Each template parameter type shall meet the
requirements (30.2.5.2).
MutexLockable, except that a call to[Note: Thetry_lock()
may throw an exception.unique_lock
class template meets these requirements when suitably instantiated. — end note]
Replace 30.4.4 [thread.lock.algorithm] p4 with the following:
template <class L1, class L2, class... L3> void lock(L1&, L2&, L3&...);4 Requires: Each template parameter type shall meet the
requirements (30.2.5.2).
MutexLockable, except that a call to[Note: Thetry_lock()
may throw an exception.unique_lock
class template meets these requirements when suitably instantiated. — end note]
Replace 30.5.2 [thread.condition.condvarany] paragraph 1 with:
1 A
Lock
type shall meet therequirements for aMutex
type, except thattry_lock
is not requiredBasicLockable
requirements (30.2.5.1). [Note: All of the standard mutex types meet this requirement. If aLock
type other than one of the standard mutex types (or aunique_lock
wrapper for a standard mutex type) is used withcondition_variable_any
then the user must ensure that any necessary synchronization is in place with respect to the predicate associated with thecondition_variable_any
instance. — end note]