2 * Copyright 2016 Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * This module implements a Synchronized abstraction useful in
19 * mutex-based concurrency.
21 * @author: Andrei Alexandrescu (andrei.alexandrescu@fb.com)
26 #include <boost/thread.hpp>
27 #include <folly/LockTraits.h>
28 #include <folly/LockTraitsBoost.h>
29 #include <folly/Preprocessor.h>
30 #include <folly/SharedMutex.h>
31 #include <folly/Traits.h>
33 #include <type_traits>
38 enum InternalDoNotUse {};
42 * Synchronized<T> encapsulates an object of type T (a "datum") paired
43 * with a mutex. The only way to access the datum is while the mutex
44 * is locked, and Synchronized makes it virtually impossible to do
45 * otherwise. The code that would access the datum in unsafe ways
46 * would look odd and convoluted, thus readily alerting the human
47 * reviewer. In contrast, the code that uses Synchronized<T> correctly
48 * looks simple and intuitive.
50 * The second parameter must be a mutex type. Any mutex type supported by
51 * LockTraits<Mutex> can be used. By default any class with lock() and
52 * unlock() methods will work automatically. LockTraits can be specialized to
53 * teach Locked how to use other custom mutex types. See the documentation in
54 * LockTraits.h for additional details.
56 * Supported mutexes that work by default include std::mutex,
57 * std::recursive_mutex, std::timed_mutex, std::recursive_timed_mutex,
58 * folly::SharedMutex, folly::RWSpinLock, and folly::SpinLock.
59 * Include LockTraitsBoost.h to get additional LockTraits specializations to
60 * support the following boost mutex types: boost::mutex,
61 * boost::recursive_mutex, boost::shared_mutex, boost::timed_mutex, and
62 * boost::recursive_timed_mutex.
64 template <class T, class Mutex = SharedMutex>
67 * Default constructor leaves both members call their own default
70 Synchronized() = default;
73 static constexpr bool nxCopyCtor{
74 std::is_nothrow_copy_constructible<T>::value};
75 static constexpr bool nxMoveCtor{
76 std::is_nothrow_move_constructible<T>::value};
79 * Helper constructors to enable Synchronized for
80 * non-default constructible types T.
81 * Guards are created in actual public constructors and are alive
82 * for the time required to construct the object
84 template <typename Guard>
85 Synchronized(const Synchronized& rhs,
86 const Guard& /*guard*/) noexcept(nxCopyCtor)
87 : datum_(rhs.datum_) {}
89 template <typename Guard>
90 Synchronized(Synchronized&& rhs, const Guard& /*guard*/) noexcept(nxMoveCtor)
91 : datum_(std::move(rhs.datum_)) {}
95 * Copy constructor copies the data (with locking the source and
96 * all) but does NOT copy the mutex. Doing so would result in
99 Synchronized(const Synchronized& rhs) noexcept(nxCopyCtor)
100 : Synchronized(rhs, rhs.operator->()) {}
103 * Move constructor moves the data (with locking the source and all)
104 * but does not move the mutex.
106 Synchronized(Synchronized&& rhs) noexcept(nxMoveCtor)
107 : Synchronized(std::move(rhs), rhs.operator->()) {}
110 * Constructor taking a datum as argument copies it. There is no
111 * need to lock the constructing object.
113 explicit Synchronized(const T& rhs) noexcept(nxCopyCtor) : datum_(rhs) {}
116 * Constructor taking a datum rvalue as argument moves it. Again,
117 * there is no need to lock the constructing object.
119 explicit Synchronized(T&& rhs) noexcept(nxMoveCtor)
120 : datum_(std::move(rhs)) {}
123 * Lets you construct non-movable types in-place. Use the constexpr
124 * instance `construct_in_place` as the first argument.
126 template <typename... Args>
127 explicit Synchronized(construct_in_place_t, Args&&... args)
128 : datum_(std::forward<Args>(args)...) {}
131 * The canonical assignment operator only assigns the data, NOT the
132 * mutex. It locks the two objects in ascending order of their
135 Synchronized& operator=(const Synchronized& rhs) {
137 // Self-assignment, pass.
138 } else if (this < &rhs) {
139 auto guard1 = operator->();
140 auto guard2 = rhs.operator->();
143 auto guard1 = rhs.operator->();
144 auto guard2 = operator->();
151 * Move assignment operator, only assigns the data, NOT the
152 * mutex. It locks the two objects in ascending order of their
155 Synchronized& operator=(Synchronized&& rhs) {
157 // Self-assignment, pass.
158 } else if (this < &rhs) {
159 auto guard1 = operator->();
160 auto guard2 = rhs.operator->();
161 datum_ = std::move(rhs.datum_);
163 auto guard1 = rhs.operator->();
164 auto guard2 = operator->();
165 datum_ = std::move(rhs.datum_);
171 * Lock object, assign datum.
173 Synchronized& operator=(const T& rhs) {
174 auto guard = operator->();
180 * Lock object, move-assign datum.
182 Synchronized& operator=(T&& rhs) {
183 auto guard = operator->();
184 datum_ = std::move(rhs);
189 * A LockedPtr lp keeps a modifiable (i.e. non-const)
190 * Synchronized<T> object locked for the duration of lp's
191 * existence. Because of this, you get to access the datum's methods
192 * directly by using lp->fun().
196 * Found no reason to leave this hanging.
198 LockedPtr() = delete;
201 * Takes a Synchronized and locks it.
203 explicit LockedPtr(Synchronized* parent) : parent_(parent) {
208 * Takes a Synchronized and attempts to lock it for some
209 * milliseconds. If not, the LockedPtr will be subsequently null.
211 LockedPtr(Synchronized* parent, unsigned int milliseconds) {
212 std::chrono::milliseconds chronoMS(milliseconds);
213 if (LockTraits<Mutex>::try_lock_for(parent->mutex_, chronoMS)) {
217 // Could not acquire the resource, pointer is null
222 * This is used ONLY inside SYNCHRONIZED_DUAL. It initializes
223 * everything properly, but does not lock the parent because it
224 * "knows" someone else will lock it. Please do not use.
226 LockedPtr(Synchronized* parent, detail::InternalDoNotUse)
231 * Copy ctor adds one lock.
233 LockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) {
238 * Assigning from another LockedPtr results in freeing the former
239 * lock and acquiring the new one. The method works with
240 * self-assignment (does nothing).
242 LockedPtr& operator=(const LockedPtr& rhs) {
243 if (parent_ != rhs.parent_) {
244 if (parent_) parent_->mutex_.unlock();
245 parent_ = rhs.parent_;
252 * Destructor releases.
256 LockTraits<Mutex>::unlock(parent_->mutex_);
261 * Safe to access the data. Don't save the obtained pointer by
262 * invoking lp.operator->() by hand. Also, if the method returns a
263 * handle stored inside the datum, don't use this idiom - use
264 * SYNCHRONIZED below.
267 return parent_ ? &parent_->datum_ : nullptr;
271 * This class temporarily unlocks a LockedPtr in a scoped
272 * manner. It is used inside of the UNSYNCHRONIZED macro.
274 struct Unsynchronizer {
275 explicit Unsynchronizer(LockedPtr* p) : parent_(p) {
276 LockTraits<Mutex>::unlock(parent_->parent_->mutex_);
278 Unsynchronizer(const Unsynchronizer&) = delete;
279 Unsynchronizer& operator=(const Unsynchronizer&) = delete;
283 LockedPtr* operator->() const {
289 friend struct Unsynchronizer;
290 Unsynchronizer typeHackDoNotUse();
292 template <class P1, class P2>
293 friend void lockInOrder(P1& p1, P2& p2);
298 LockTraits<Mutex>::lock(parent_->mutex_);
302 // This is the entire state of LockedPtr.
303 Synchronized* parent_;
307 * ConstLockedPtr does exactly what LockedPtr does, but for const
308 * Synchronized objects. Of interest is that ConstLockedPtr only
309 * uses a read lock, which is faster but more restrictive - you only
310 * get to call const methods of the datum.
312 * Much of the code between LockedPtr and
313 * ConstLockedPtr is identical and could be factor out, but there
314 * are enough nagging little differences to not justify the trouble.
316 struct ConstLockedPtr {
317 ConstLockedPtr() = delete;
318 explicit ConstLockedPtr(const Synchronized* parent) : parent_(parent) {
321 ConstLockedPtr(const Synchronized* parent, detail::InternalDoNotUse)
324 ConstLockedPtr(const ConstLockedPtr& rhs) : parent_(rhs.parent_) {
327 explicit ConstLockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) {
330 ConstLockedPtr(const Synchronized* parent, unsigned int milliseconds) {
331 if (try_lock_shared_or_unique_for(
332 parent->mutex_, std::chrono::milliseconds(milliseconds))) {
336 // Could not acquire the resource, pointer is null
340 ConstLockedPtr& operator=(const ConstLockedPtr& rhs) {
341 if (parent_ != rhs.parent_) {
342 if (parent_) parent_->mutex_.unlock_shared();
343 parent_ = rhs.parent_;
349 unlock_shared_or_unique(parent_->mutex_);
353 const T* operator->() const {
354 return parent_ ? &parent_->datum_ : nullptr;
357 struct Unsynchronizer {
358 explicit Unsynchronizer(ConstLockedPtr* p) : parent_(p) {
359 unlock_shared_or_unique(parent_->parent_->mutex_);
361 Unsynchronizer(const Unsynchronizer&) = delete;
362 Unsynchronizer& operator=(const Unsynchronizer&) = delete;
364 lock_shared_or_unique(parent_->parent_->mutex_);
366 ConstLockedPtr* operator->() const {
370 ConstLockedPtr* parent_;
372 friend struct Unsynchronizer;
373 Unsynchronizer typeHackDoNotUse();
375 template <class P1, class P2>
376 friend void lockInOrder(P1& p1, P2& p2);
381 lock_shared_or_unique(parent_->mutex_);
385 const Synchronized* parent_;
389 * This accessor offers a LockedPtr. In turn. LockedPtr offers
390 * operator-> returning a pointer to T. The operator-> keeps
391 * expanding until it reaches a pointer, so syncobj->foo() will lock
392 * the object and call foo() against it.
394 LockedPtr operator->() {
395 return LockedPtr(this);
399 * Same, for constant objects. You will be able to invoke only const
402 ConstLockedPtr operator->() const {
403 return ConstLockedPtr(this);
407 * Attempts to acquire for a given number of milliseconds. If
408 * acquisition is unsuccessful, the returned LockedPtr is NULL.
410 LockedPtr timedAcquire(unsigned int milliseconds) {
411 return LockedPtr(this, milliseconds);
415 * As above, for a constant object.
417 ConstLockedPtr timedAcquire(unsigned int milliseconds) const {
418 return ConstLockedPtr(this, milliseconds);
422 * Used by SYNCHRONIZED_DUAL.
424 LockedPtr internalDoNotUse() {
425 return LockedPtr(this, detail::InternalDoNotUse());
431 ConstLockedPtr internalDoNotUse() const {
432 return ConstLockedPtr(this, detail::InternalDoNotUse());
436 * Sometimes, although you have a mutable object, you only want to
437 * call a const method against it. The most efficient way to achieve
438 * that is by using a read lock. You get to do so by using
439 * obj.asConst()->method() instead of obj->method().
441 const Synchronized& asConst() const {
446 * Swaps with another Synchronized. Protected against
447 * self-swap. Only data is swapped. Locks are acquired in increasing
450 void swap(Synchronized& rhs) {
455 return rhs.swap(*this);
457 auto guard1 = operator->();
458 auto guard2 = rhs.operator->();
461 swap(datum_, rhs.datum_);
465 * Swap with another datum. Recommended because it keeps the mutex
469 LockedPtr guard = operator->();
476 * Copies datum to a given target.
478 void copy(T* target) const {
479 ConstLockedPtr guard = operator->();
484 * Returns a fresh copy of the datum.
487 ConstLockedPtr guard = operator->();
493 mutable Mutex mutex_;
496 // Non-member swap primitive
497 template <class T, class M>
498 void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) {
503 * SYNCHRONIZED is the main facility that makes Synchronized<T>
504 * helpful. It is a pseudo-statement that introduces a scope where the
505 * object is locked. Inside that scope you get to access the unadorned
510 * Synchronized<vector<int>> svector;
512 * SYNCHRONIZED (svector) { ... use svector as a vector<int> ... }
514 * SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... }
516 * Refer to folly/docs/Synchronized.md for a detailed explanation and more
519 #define SYNCHRONIZED(...) \
521 FOLLY_GCC_DISABLE_WARNING(shadow) \
522 if (bool SYNCHRONIZED_state = false) { \
524 for (auto SYNCHRONIZED_lockedPtr = \
525 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).operator->(); \
526 !SYNCHRONIZED_state; \
527 SYNCHRONIZED_state = true) \
528 for (auto& FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \
529 *SYNCHRONIZED_lockedPtr.operator->(); \
530 !SYNCHRONIZED_state; \
531 SYNCHRONIZED_state = true) \
534 #define TIMED_SYNCHRONIZED(timeout, ...) \
535 if (bool SYNCHRONIZED_state = false) { \
537 for (auto SYNCHRONIZED_lockedPtr = \
538 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).timedAcquire(timeout); \
539 !SYNCHRONIZED_state; \
540 SYNCHRONIZED_state = true) \
541 for (auto FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \
542 SYNCHRONIZED_lockedPtr.operator->(); \
543 !SYNCHRONIZED_state; \
544 SYNCHRONIZED_state = true)
547 * Similar to SYNCHRONIZED, but only uses a read lock.
549 #define SYNCHRONIZED_CONST(...) \
551 FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
552 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).asConst())
555 * Similar to TIMED_SYNCHRONIZED, but only uses a read lock.
557 #define TIMED_SYNCHRONIZED_CONST(timeout, ...) \
558 TIMED_SYNCHRONIZED( \
560 FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
561 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).asConst())
564 * Temporarily disables synchronization inside a SYNCHRONIZED block.
566 #define UNSYNCHRONIZED(name) \
567 for (decltype(SYNCHRONIZED_lockedPtr.typeHackDoNotUse()) \
568 SYNCHRONIZED_state3(&SYNCHRONIZED_lockedPtr); \
569 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
570 for (auto& name = *SYNCHRONIZED_state3.operator->(); \
571 !SYNCHRONIZED_state; SYNCHRONIZED_state = true)
574 * Locks two objects in increasing order of their addresses.
576 template <class P1, class P2>
577 void lockInOrder(P1& p1, P2& p2) {
578 if (static_cast<const void*>(p1.operator->()) >
579 static_cast<const void*>(p2.operator->())) {
589 * Synchronizes two Synchronized objects (they may encapsulate
590 * different data). Synchronization is done in increasing address of
591 * object order, so there is no deadlock risk.
593 #define SYNCHRONIZED_DUAL(n1, e1, n2, e2) \
594 if (bool SYNCHRONIZED_state = false) {} else \
595 for (auto SYNCHRONIZED_lp1 = (e1).internalDoNotUse(); \
596 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
597 for (auto& n1 = *SYNCHRONIZED_lp1.operator->(); \
598 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
599 for (auto SYNCHRONIZED_lp2 = (e2).internalDoNotUse(); \
600 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
601 for (auto& n2 = *SYNCHRONIZED_lp2.operator->(); \
602 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
603 if ((::folly::lockInOrder( \
604 SYNCHRONIZED_lp1, SYNCHRONIZED_lp2), \
608 } /* namespace folly */