32 Concurrency support library [thread]
A hazard pointer is a single-writer multi-reader pointer
that can be owned by at most one thread at any time
. Only the owner of the hazard pointer can set its value,
while any number of threads may read its value
. The owner thread sets the value of a hazard pointer to point to an object
in order to indicate to concurrent threads—which
may delete such an object—that
the object is not yet safe to delete
.A class type
T is
hazard-protectable
if it has exactly one base class of type
hazard_pointer_obj_base<T, D>
for some
D,
that base is public and non-virtual, and
it has no base classes of type
hazard_pointer_obj_base<T2, D2>
for any other combination
T2,
D2. Upon creation, a hazard pointer is unassociated
. Changing the association (possibly to the same object)
initiates a new protection epoch and ends the preceding one
. An object
x of hazard-protectable type
T is
retired with a deleter of type
D
when the member function
hazard_pointer_obj_base<T, D>::retire
is invoked on
x. Any given object
x shall be retired at most once
.A retired object
x is
reclaimed
by invoking its deleter with a pointer to
x;
the behavior is undefined if that invocation exits via an exception
.A hazard-protectable object
x is
possibly-reclaimable
with respect to an evaluation
A if
- x is not reclaimed; and
- x is retired in an evaluation R and
A does not happen before R; and
- for all hazard pointers h and for every protection epoch E of h
during which h is associated with x:
- if the beginning of E happens before R,
the end of E strongly happens before A; and
- if E began by an evaluation of try_protect with argument src,
label its atomic load operation L.
If there exists an atomic modification
B on
src
such that
L observes a modification that is modification-ordered before
B, and
B happens before
x is retired,
the end of
E strongly happens before
A. [
Note 1:
In typical use, a store to
src sequenced before retiring
x
will be such an atomic operation
B. —
end note]
[
Note 2:
The latter two conditions convey the informal notion
that a protection epoch that began before retiring
x,
as implied either by the happens-before relation or
the coherence order of some source,
delays the reclamation of
x. —
end note]
The number of possibly-reclaimable objects has an unspecified bound
. [
Note 3:
The bound can be a function of the number of hazard pointers,
the number of threads that retire objects, and
the number of threads that use hazard pointers
. —
end note]
[
Example 1:
The following example shows how hazard pointers allow updates to be carried out
in the presence of concurrent readers
. The object of type
hazard_pointer in
print_name
protects the object
*ptr from being reclaimed by
ptr->retire
until the end of the protection epoch
. struct Name : public hazard_pointer_obj_base<Name> { };
atomic<Name*> name;
void print_name() {
hazard_pointer h = make_hazard_pointer();
Name* ptr = h.protect(name);
}
void update_name(Name* new_name) {
Name* ptr = name.exchange(new_name);
ptr->retire();
}
—
end example]
namespace std {
template<class T, class D = default_delete<T>>
class hazard_pointer_obj_base {
public:
void retire(D d = D()) noexcept;
protected:
hazard_pointer_obj_base() = default;
hazard_pointer_obj_base(const hazard_pointer_obj_base&) = default;
hazard_pointer_obj_base(hazard_pointer_obj_base&&) = default;
hazard_pointer_obj_base& operator=(const hazard_pointer_obj_base&) = default;
hazard_pointer_obj_base& operator=(hazard_pointer_obj_base&&) = default;
~hazard_pointer_obj_base() = default;
private:
D deleter;
};
}
D shall be a function object type (
[func.require])
for which, given a value
d of type
D and
a value
ptr of type
T*,
the expression
d(ptr) is valid
. The behavior of a program
that adds specializations for
hazard_pointer_obj_base is undefined
.T may be an incomplete type
. It shall be complete before any member
of the resulting specialization of
hazard_pointer_obj_base
is referenced
.void retire(D d = D()) noexcept;
Mandates:
T is a hazard-protectable type
. Preconditions:
*this is
a base class subobject of an object
x of type
T. Move-assigning
d to
deleter does not exit via an exception
.Effects: Move-assigns
d to
deleter,
thereby setting it as the deleter of
x,
then retires
x. May reclaim possibly-reclaimable objects
.namespace std {
class hazard_pointer {
public:
hazard_pointer() noexcept;
hazard_pointer(hazard_pointer&&) noexcept;
hazard_pointer& operator=(hazard_pointer&&) noexcept;
~hazard_pointer();
bool empty() const noexcept;
template<class T> T* protect(const atomic<T*>& src) noexcept;
template<class T> bool try_protect(T*& ptr, const atomic<T*>& src) noexcept;
template<class T> void reset_protection(const T* ptr) noexcept;
void reset_protection(nullptr_t = nullptr) noexcept;
void swap(hazard_pointer&) noexcept;
};
}
An object of type
hazard_pointer is either empty or
owns a hazard pointer
. Each hazard pointer is owned by
exactly one object of type
hazard_pointer. [
Note 1:
An empty
hazard_pointer object is different from
a
hazard_pointer object
that owns an unassociated hazard pointer
. An empty
hazard_pointer object does not own any hazard pointers
. —
end note]
hazard_pointer() noexcept;
Postconditions:
*this is empty
. hazard_pointer(hazard_pointer&& other) noexcept;
Postconditions: If
other is empty,
*this is empty
. Otherwise,
*this owns the hazard pointer originally owned by
other;
other is empty
.Effects: If
*this is not empty,
destroys the hazard pointer owned by
*this,
thereby ending its current protection epoch
. hazard_pointer& operator=(hazard_pointer&& other) noexcept;
Effects: If
this == &other is
true, no effect
. Otherwise, if
*this is not empty,
destroys the hazard pointer owned by
*this,
thereby ending its current protection epoch
.Postconditions: If
other was empty,
*this is empty
. Otherwise,
*this owns the hazard pointer originally
owned by
other. If
this != &other is
true,
other is empty
.bool empty() const noexcept;
Returns:
true if and only if
*this is empty
. template<class T> T* protect(const atomic<T*>& src) noexcept;
Effects: Equivalent to:
T* ptr = src.load(memory_order::relaxed);
while (!try_protect(ptr, src)) {}
return ptr;
template<class T> bool try_protect(T*& ptr, const atomic<T*>& src) noexcept;
Mandates:
T is a hazard-protectable type
. Preconditions:
*this is not empty
. Effects: Performs the following steps in order:
Initializes a variable
old of type
T* with the value of
ptr.Evaluates
reset_protection(old).Assigns the value of
src.load(memory_order::acquire) to
ptr.If
old == ptr is
false,
evaluates
reset_protection().
template<class T> void reset_protection(const T* ptr) noexcept;
Mandates:
T is a hazard-protectable type
. Preconditions:
*this is not empty
. Effects: If
ptr is a null pointer value, invokes
reset_protection(). Otherwise,
associates the hazard pointer owned by
*this with
*ptr,
thereby ending the current protection epoch
.void reset_protection(nullptr_t = nullptr) noexcept;
Preconditions:
*this is not empty
. Postconditions: The hazard pointer owned by
*this is unassociated
. void swap(hazard_pointer& other) noexcept;
Effects: Swaps the hazard pointer ownership of this object with that of
other. [
Note 1:
The owned hazard pointers, if any, remain unchanged during the swap and
continue to be associated with the respective objects
that they were protecting before the swap, if any
. No protection epochs are ended or initiated
. —
end note]
hazard_pointer make_hazard_pointer();
Effects: Constructs a hazard pointer
. Returns: A
hazard_pointer object that owns the newly-constructed hazard pointer
. Throws: May throw
bad_alloc
if memory for the hazard pointer could not be allocated
. void swap(hazard_pointer& a, hazard_pointer& b) noexcept;
Effects: Equivalent to
a.swap(b).