1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
|
Locks are used to simplify the use of mutexes. Before locks can be used the
tthi(mutex) header file must be included.
Whenever threads share data, and at least one of the threads may change common
data, mutexes should be used to prevent threads from using the same data
synchronously.
Usually locks are released at the end of action blocks. This requires explicit
calls to the mutexes' tt(unlock) function, which introduces comparable
problems as we've seen with the thread's tt(join) member.
To simplify locking and unlocking two mutex wrapper classes are available:
itemization(
ithtq(lock_guard)(std::lock_guard)
(objects of this class offer the basic unlock-guarantee: their
destructors call the member tt(unlock) of the mutexes they control;)
ithtq(unique_lock)(std::unique_lock)
(objects of this class offer a more extensive interface, allowing
explicit unlocking and locking of the mutexes they control, while their
destructors preserve the unlock-guarantee also offered by tt(lock_guard);)
)
The class tt(lock_guard) offers a limited, but useful interface:
itemization(
ittq(lock_guard<Mutex>(Mutex &mutex))
(when defining a tt(lock_guard) object the mutex type (e.g.,
tt(std::mutex, std::timed_mutex, std::shared_mutex)) is specified, and a mutex
of the indicated type is provided as its argument. The construction blocks
until the tt(lock_guard) object owns the lock. The tt(lock_guard's) destructor
automatically releases the mutex lock.)
ittq(lock_guard<Mutex>(Mutex &mutex, std::adopt_lock_t))
(this constructor is used to transfer control over the mutex from the
calling thread to the tt(lock_guard). The mutex lock is released again by the
tt(lock_guard's) destructor. At construction time the mutex must already be
owned by the calling thread. Here is an illustration of how it can be used:
verbinclude(-n //code examples/threadaction.cc)
itemization(
it() At line 1 tt(threadAction) receives a reference to a
mutex. Assume the mutex owns the lock;
it() At line 3 control is transferred to the tt(lock_guard). Even
though we don't explicitly use the tt(lock_guard) object, an object should be
defined to prevent the compiler from destroying an anonymous object before the
function ends;
it() When the function ends, at line 5, the mutex's lock is
released by the tt(lock_guard's) destructor.
)
)
ittq(mutex_type)
(in hi(mutex_type) addition to the constructors and destructor,
tt(lock_guard<Mutex>) types also define the type ti(mutex_type): it is a
synonym of the tt(Mutex) type that is passed to the tt(lock_guard)'s
constructor.)
)
Here is a simple example of a multi-threaded program using tt(lock_guards)
to prevent information inserted into tt(cout) from getting mixed.
verbinclude(-s4 //code examples/insertguard.cc)
As with tt(lock_guard), a mutex-type must be specified when defining
objects of the class hi(unique_lock)tt(std::unique_lock). The class
tt(unique_lock) is much more elaborate than the basic tt(lock_guard) class
template. Its interface does not define a copy constructor or overloaded
assignment operator, but it em(does) define a move constructor and a move
assignment operator. In the following overview of tt(unique_lock)'s inteface
tt(Mutex) refers to the mutex-type that is specified when defining a
tt(unique_lock):
itemization(
ittq(unique_lock() noexcept)
(the default constructor is not yet associated with a tt(mutex)
object. It must be assigned a tt(mutex) (e.g., using move-assignment) before
it can do anything useful;)
ittq(explicit unique_lock(Mutex &mutex))
(initializes a tt(unique_lock) with an existing tt(Mutex) object, and
calls tt(mutex.lock());)
ittq(unique_lock(Mutex &mutex, defer_lock_t) noexcept)
(initializes a tt(unique_lock) with an existing tt(Mutex) object, but
does not call tt(mutex.lock()). Call it by passing a tt(defer_lock_t) object
as the constructor's second argument, e.g.,
verb(unique_lock<mutex> ul(mutexObj, defer_lock_t()))
)
ittq(unique_lock(Mutex &mutex, try_to_lock_t) noexcept)
(initializes a tt(unique_lock) with an existing tt(Mutex) object, and
calls tt(mutex.try_lock()): the constructor won't block if the mutex cannot be
locked;)
ittq(unique_lock(Mutex &mutex, adopt_lock_t) noexcept)
(initializes a tt(unique_lock) with an existing tt(Mutex) object,
and assumes that the current thread has already locked the mutex;)
ittq(unique_lock(Mutex &mutex, chrono::duration<Rep, Period> const
&relTime) noexcept)
(this constructor tries to obtain ownership of the tt(Mutex) object by
calling tt(mutex.try_lock_for(relTime)). The specified mutex type must
therefore support this member (e.g., it is a tt(std::timed_mutex)). It could
be called like this:
verb(std::unique_lock<std::timed_mutex> ulock(timedMutex,
std::chrono::seconds(5));)
)
ittq(unique_lock(Mutex &mutex, chrono::time_point<Clock, Duration> const
&absTime) noexcept)
(this constructor tries to obtain ownership of the tt(Mutex) object by
calling tt(mutex.try_lock_until(absTime)). The specified mutex type must
therefore support this member (e.g., it is a tt(std::timed_mutex)).
This constructor could be called like this:
verb(std::unique_lock<std::timed_mutex> ulock(
timedMutex,
std::chrono::system_clock::now() + std::chrono::seconds(5)
);)
)
ithtq(lock)(void lock())
(blocks the current thread until ownership of the mutex that is managed
by the tt(unique_lock) is obtained. If no mutex is currently managed, then a
tt(system_error) exception is thrown.)
ithtq(mutex)(Mutex *mutex() const noexcept)
(returns a pointer to the mutex object stored inside the
tt(unique_lock) (a tt(nullptr) is returned if no mutex object is currently
associated with the tt(unique_lock) object.))
ittq(explicit operator bool() const noexcept)
(returns tt(true) if the tt(unique_lock) owns a locked mutex, otherwise
tt(false) is returned;)
ittq(unique_lock& operator=(unique_lock &&tmp) noexcept)
(if the left-hand operand owns a lock, it will call its mutex's
tt(unlock) member, whereafter tt(tmp's) state is transferred to the left-hand
operand;)
ithtq(owns_lock)(bool owns_lock() const noexcept)
(returns tt(true) if the tt(unique_lock) owns the mutex, otherwise
tt(false) is returned;)
ithtq(release)(Mutex *release() noexcept)
(returns a pointer to the mutex object that is associated with the
tt(unique_lock) object, discarding that association;)
ithtq(swap)(void swap(unique_lock& other) noexcept)
(swaps the states of the current tt(unique_lock) and tt(other);)
ithtq(try_lock)(bool try_lock())
(tries to obtain ownership of the mutex that is associated witg the
tt(unique_lock), returning tt(true) if this succeeds, and tt(false)
otherwise. If no mutex is currently associated with the tt(unique_lock)
object, then a tt(system_error) exception is thrown;)
ithtq(try_lock_for)(bool try_lock_for(chrono::duration<Rep, Period> const
&relTime))
(this member function tries to obtain ownership of the tt(Mutex) object
managed by the tt(unique_lock) object by calling the mutex's
tt(try_lock_for(relTime)) member. The specified mutex type must therefore
support this member (e.g., it is a tt(std::timed_mutex));)
ithtq(try_lock_until)(bool try_lock_until(chrono::time_point<Clock,
Duration> const &absTime))
(this member function tries to obtain ownership of the tt(Mutex) object
managed by the tt(unique_lock) object by calling the mutex's
tt(mutex.try_lock_until(absTime)) member. The specified mutex type must
therefore support this member (e.g., it is a tt(std::timed_mutex));)
ithtq(unlock)(void unlock())
(releases ownership of the mutex (or reduces the mutex's lock count). A
tt(system_error) exception is thrown if the tt(unique_lock) object does not
own the mutex.)
)
In addition to the members of the classes tt(std::lock_guard) and
tt(std::unique_lock) the functions tt(std::lock)hi(lock (function)) and
tt(std::try_lock)hi(try_lock (function)) are available. These functions can be
used to prevent em(deadlocks), the topic of the next section.
|