Document Number:N3130=10-0120
Date:2010-08-19
Author:Anthony Williams
Just Software Solutions Ltd

N3130: Lockable requirements for C++0x

This paper 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.

Proposed wording

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

A concurrent agent is a an entity (such as a thread, process, or packaged task) that may perform work in parallel with other concurrent agents. The calling agent is determined by context, e.g. the calling thread or the packaged task that contains the call, etc.

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]) and condition_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 the BasicLockable requirements, the Lockable requirements or the TimedLockable requirements as appropriate to acquire or release ownership of a lock by a given concurrent agent. [Note: the nature of any lock ownership and any synchronization it may entail are not part of these requirements. — end note]

[ Note: Some lockable objects are “agent-oblivious” in that they work for any concurrent-agent model because they do not determine or store the agent’s ID (e.g., an ordinary spin-lock). – end note ]

30.2.5.1 BasicLockable Requirements

In order for a type L to qualify as a BasicLockable type, the following expressions must be supported, with the specified semantics, where m denotes a value of type L:

The expression m.lock() shall be well-formed and have the following semantics:

Effects:
Block until a lock can be acquired for the current concurrent agent. If an exception is thrown then a lock shall not have been acquired for the current concurrent agent.
Return type:
void

The expression m.unlock() shall be well-formed and have the following semantics:

Requires:
The current concurrent agent shall hold a lock on m.
Effects:
Release a lock on m held by the current concurrent agent.
Return type:
void
Throws:
Nothing.

30.2.5.2 Lockable Requirements

In order for a type L to qualify as a Lockable type, it must meet the BasicLockable requirements. In addition, the following expressions must be supported, with the specified semantics, where m denotes a value of type L:

The expression m.try_lock() shall be well-formed and have the following semantics:

Effects:
Attempt to acquire a lock for the current concurrent agent without blocking. If an exception is thrown then a lock shall not have been acquired for the current concurrent agent.
Return type:
bool
Returns:
true if the lock was acquired, false otherwise.

30.2.5.3 TimedLockable Requirements

For a type TL to qualify as TimedLockable it must meet the Lockable requirements, and additionally the following expressions must be well-formed, with the specified semantics, where m is an instance of a type TL, rel_time denotes instantiation of duration (20.10.3 [time.duration]) and abs_time denotes an instantiation of time_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 concurrent agent within the relative timeout (30.2.4 [thread.req.timing]) specified by rel_time. The function shall return within the timeout specified by rel_time only if it has obtained a lock on m for the current concurrent agent. If an exception is thrown then a lock shall not have been acquired for the current concurrent 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 concurrent agent before the absolute timeout (30.2.4 [thread.req.timing]) specified by abs_time. The function shall return before the timeout specified by abs_time only if it has obtained a lock on m for the current concurrent agent. If an exception is thrown then a lock shall not have been acquired for the current concurrent 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 between threadsconcurrent agents (30.2.5). A threadconcurrent 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 many threadsconcurrent 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 in the mutex types supplied by the C++ standard library. The template definitions in the C++ standard library refer These types shall conform to the named Mutex requirements whose details are set out below. In this description, m is an object of a Mutex type one of the standard library mutex types std::mutex, std::recursive_mutex, std::timed_mutex or std::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 type std::mutex or std::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 type std::mutex or std::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 and std::recursive_timed_mutex (the timed mutex types) A TimedMutex type shall meet the requirements for a Mutex type. In addition, it shall meet the requirements set out in this Clause 30.4.2below, where rel_time denotes an instantiation of duration (20.10.3 [time.duration]) and abs_time denotes an instantiation of time_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. If m is of type std::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 type std::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 the Lockable 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 the Lockable requirements (30.2.5.2). unique_lock<Mutex> meets the Lockable requirements. If Mutex meets the TimedLockable requirements (30.2.5.3) then unique_lock<Mutex> also meets the TimedLockable 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 the mutexlockable object during the lock’s destruction (such as when leaving block scope). A thread of executionconcurrent agent may use a lock to aid in managing mutex ownership of a lockable object in an exception safe manner. A lock is said to own a mutexlockable object if it is currently managing the ownership of that mutexlockable object for a thread of executionconcurrent agent. A lock does not manage the lifetime of the mutexlockable object it references. [ Note: Locks are intended to ease the burden of unlocking the mutexlockable object under both normal and exceptional circumstances. — end note ]

30.4.3 [thread.lock] paragaph 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] paragaph 1:

1 An object of type lock_guard controls the ownership of a mutexlockable object within a scope. A lock_guard object maintains ownership of a mutexlockable object throughout the lock_guard object’s lifetime. The behavior of a program is undefined if the mutexlockable object referenced by pm does not exist for the entire lifetime (3.8) of the lock_guard object. Mutex shall meet the BasicLockable requirements (30.2.5.2).

30.4.3.2 [thread.lock.unique] paragaph 1:

1 An object of type unique_lock controls the ownership of a mutexlockable object within a scope. Mutex oOwnership of the lockable object may be acquired at construction or after construction, and may be transferred, after acquisition, to another unique_lock object. Objects of type unique_lock are not copyable but are movable. The behavior of a program is undefined if the contained pointer pm is not null and the mutex pointed to by pm does not exist for the entire remaining lifetime (3.8) of the unique_lock object. Mutex shall meet the Lockable 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 supplied mutex_type type shall meet the TimedLockable 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 supplied mutex_type type shall meet the TimedLockable 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 the TimedLockable 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 the TimedLockable 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 Mutex Lockable requirements (30.2.5.2)., except that a call to try_lock() may throw an exception. [Note: The 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 Mutex Lockable requirements (30.2.5.2)., except that a call to try_lock() may throw an exception. [Note: The 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 the requirements for a Mutex type, except that try_lock is not required BasicLockable requirements (30.2.5.1). [Note: All of the standard mutex types meet this requirement. — end note]