2 * Copyright 2012 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)
24 #ifndef SYNCHRONIZED_H_
25 #define SYNCHRONIZED_H_
27 #include <type_traits>
29 #include <boost/thread.hpp>
30 #include "folly/Preprocessor.h"
31 #include "folly/Traits.h"
36 enum InternalDoNotUse {};
39 * Free function adaptors for std:: and boost::
43 * Yields true iff T has .lock() and .unlock() member functions. This
44 * is done by simply enumerating the mutexes with this interface in
48 struct HasLockUnlock {
49 enum { value = IsOneOf<T,
50 std::mutex, std::recursive_mutex,
51 std::timed_mutex, std::recursive_timed_mutex,
52 boost::mutex, boost::recursive_mutex, boost::shared_mutex,
53 boost::timed_mutex, boost::recursive_timed_mutex
58 * Acquires a mutex for reading by calling .lock(). The exception is
59 * boost::shared_mutex, which has a special read-lock primitive called
63 typename std::enable_if<
64 HasLockUnlock<T>::value && !std::is_same<T, boost::shared_mutex>::value>::type
65 acquireRead(T& mutex) {
70 * Special case for boost::shared_mutex.
73 typename std::enable_if<std::is_same<T, boost::shared_mutex>::value>::type
74 acquireRead(T& mutex) {
79 * Acquires a mutex for reading with timeout by calling .timed_lock(). This
80 * applies to three of the boost mutex classes as enumerated below.
83 typename std::enable_if<std::is_same<T, boost::shared_mutex>::value, bool>::type
85 unsigned int milliseconds) {
86 return mutex.timed_lock_shared(boost::posix_time::milliseconds(milliseconds));
90 * Acquires a mutex for reading and writing by calling .lock().
93 typename std::enable_if<HasLockUnlock<T>::value>::type
94 acquireReadWrite(T& mutex) {
99 * Acquires a mutex for reading and writing with timeout by calling
100 * .try_lock_for(). This applies to two of the std mutex classes as
104 typename std::enable_if<
105 IsOneOf<T, std::timed_mutex, std::recursive_timed_mutex>::value, bool>::type
106 acquireReadWrite(T& mutex,
107 unsigned int milliseconds) {
108 return mutex.try_lock_for(std::chrono::milliseconds(milliseconds));
112 * Acquires a mutex for reading and writing with timeout by calling
113 * .timed_lock(). This applies to three of the boost mutex classes as
117 typename std::enable_if<
118 IsOneOf<T, boost::shared_mutex, boost::timed_mutex,
119 boost::recursive_timed_mutex>::value, bool>::type
120 acquireReadWrite(T& mutex,
121 unsigned int milliseconds) {
122 return mutex.timed_lock(boost::posix_time::milliseconds(milliseconds));
126 * Releases a mutex previously acquired for reading by calling
127 * .unlock(). The exception is boost::shared_mutex, which has a
128 * special primitive called .unlock_shared().
131 typename std::enable_if<
132 HasLockUnlock<T>::value && !std::is_same<T, boost::shared_mutex>::value>::type
133 releaseRead(T& mutex) {
138 * Special case for boost::shared_mutex.
141 typename std::enable_if<std::is_same<T, boost::shared_mutex>::value>::type
142 releaseRead(T& mutex) {
143 mutex.unlock_shared();
147 * Releases a mutex previously acquired for reading-writing by calling
151 typename std::enable_if<HasLockUnlock<T>::value>::type
152 releaseReadWrite(T& mutex) {
156 } // namespace detail
159 * Synchronized<T> encapsulates an object of type T (a "datum") paired
160 * with a mutex. The only way to access the datum is while the mutex
161 * is locked, and Synchronized makes it virtually impossible to do
162 * otherwise. The code that would access the datum in unsafe ways
163 * would look odd and convoluted, thus readily alerting the human
164 * reviewer. In contrast, the code that uses Synchronized<T> correctly
165 * looks simple and intuitive.
167 * The second parameter must be a mutex type. Supported mutexes are
168 * std::mutex, std::recursive_mutex, std::timed_mutex,
169 * std::recursive_timed_mutex, boost::mutex, boost::recursive_mutex,
170 * boost::shared_mutex, boost::timed_mutex,
171 * boost::recursive_timed_mutex, and the folly/RWSpinLock.h
174 * You may define Synchronized support by defining 4-6 primitives in
175 * the same namespace as the mutex class (found via ADL). The
176 * primitives are: acquireRead, acquireReadWrite, releaseRead, and
177 * releaseReadWrite. Two optional primitives for timout operations are
178 * overloads of acquireRead and acquireReadWrite. For signatures,
179 * refer to the namespace detail below, which implements the
180 * primitives for mutexes in std and boost.
182 template <class T, class Mutex = boost::shared_mutex>
183 struct Synchronized {
185 * Default constructor leaves both members call their own default
188 Synchronized() = default;
191 * Copy constructor copies the data (with locking the source and
192 * all) but does NOT copy the mutex. Doing so would result in
195 Synchronized(const Synchronized& rhs) {
196 auto guard = rhs.operator->();
201 * Move constructor moves the data (with locking the source and all)
202 * but does not move the mutex.
204 Synchronized(Synchronized&& rhs) {
205 auto guard = rhs.operator->();
206 datum_ = std::move(rhs.datum_);
210 * Constructor taking a datum as argument copies it. There is no
211 * need to lock the constructing object.
213 explicit Synchronized(const T& rhs) : datum_(rhs) {}
216 * Constructor taking a datum rvalue as argument moves it. Again,
217 * there is no need to lock the constructing object.
219 explicit Synchronized(T && rhs) : datum_(std::move(rhs)) {}
222 * The canonical assignment operator only assigns the data, NOT the
223 * mutex. It locks the two objects in ascending order of their
226 Synchronized& operator=(const Synchronized& rhs) {
228 auto guard1 = operator->();
229 auto guard2 = rhs.operator->();
232 auto guard1 = rhs.operator->();
233 auto guard2 = operator->();
240 * Lock object, assign datum.
242 Synchronized& operator=(const T& rhs) {
243 auto guard = operator->();
249 * A LockedPtr lp keeps a modifiable (i.e. non-const)
250 * Synchronized<T> object locked for the duration of lp's
251 * existence. Because of this, you get to access the datum's methods
252 * directly by using lp->fun().
256 * Found no reason to leave this hanging.
258 LockedPtr() = delete;
261 * Takes a Synchronized and locks it.
263 explicit LockedPtr(Synchronized* parent) : parent_(parent) {
268 * Takes a Synchronized and attempts to lock it for some
269 * milliseconds. If not, the LockedPtr will be subsequently null.
271 LockedPtr(Synchronized* parent, unsigned int milliseconds) {
272 using namespace detail;
273 if (acquireReadWrite(parent->mutex_, milliseconds)) {
277 // Could not acquire the resource, pointer is null
282 * This is used ONLY inside SYNCHRONIZED_DUAL. It initializes
283 * everything properly, but does not lock the parent because it
284 * "knows" someone else will lock it. Please do not use.
286 LockedPtr(Synchronized* parent, detail::InternalDoNotUse)
291 * Copy ctor adds one lock.
293 LockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) {
298 * Assigning from another LockedPtr results in freeing the former
299 * lock and acquiring the new one. The method works with
300 * self-assignment (does nothing).
302 LockedPtr& operator=(const LockedPtr& rhs) {
303 if (parent_ != rhs.parent_) {
304 if (parent_) parent_->mutex_.unlock();
305 parent_ = rhs.parent_;
312 * Destructor releases.
315 using namespace detail;
316 if (parent_) releaseReadWrite(parent_->mutex_);
320 * Safe to access the data. Don't save the obtained pointer by
321 * invoking lp.operator->() by hand. Also, if the method returns a
322 * handle stored inside the datum, don't use this idiom - use
323 * SYNCHRONIZED below.
326 return parent_ ? &parent_->datum_ : NULL;
330 * This class temporarily unlocks a LockedPtr in a scoped
331 * manner. It is used inside of the UNSYNCHRONIZED macro.
333 struct Unsynchronizer {
334 explicit Unsynchronizer(LockedPtr* p) : parent_(p) {
335 using namespace detail;
336 releaseReadWrite(parent_->parent_->mutex_);
338 Unsynchronizer(const Unsynchronizer&) = delete;
339 Unsynchronizer& operator=(const Unsynchronizer&) = delete;
343 LockedPtr* operator->() const {
349 friend struct Unsynchronizer;
350 Unsynchronizer typeHackDoNotUse();
352 template <class P1, class P2>
353 friend void lockInOrder(P1& p1, P2& p2);
357 using namespace detail;
358 if (parent_) acquireReadWrite(parent_->mutex_);
361 // This is the entire state of LockedPtr.
362 Synchronized* parent_;
366 * ConstLockedPtr does exactly what LockedPtr does, but for const
367 * Synchronized objects. Of interest is that ConstLockedPtr only
368 * uses a read lock, which is faster but more restrictive - you only
369 * get to call const methods of the datum.
371 * Much of the code between LockedPtr and
372 * ConstLockedPtr is identical and could be factor out, but there
373 * are enough nagging little differences to not justify the trouble.
375 struct ConstLockedPtr {
376 ConstLockedPtr() = delete;
377 explicit ConstLockedPtr(const Synchronized* parent) : parent_(parent) {
380 ConstLockedPtr(const Synchronized* parent, detail::InternalDoNotUse)
383 ConstLockedPtr(const ConstLockedPtr& rhs) : parent_(rhs.parent_) {
386 explicit ConstLockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) {
389 ConstLockedPtr(const Synchronized* parent, unsigned int milliseconds) {
390 if (parent->mutex_.timed_lock(
391 boost::posix_time::milliseconds(milliseconds))) {
395 // Could not acquire the resource, pointer is null
399 ConstLockedPtr& operator=(const ConstLockedPtr& rhs) {
400 if (parent_ != rhs.parent_) {
401 if (parent_) parent_->mutex_.unlock_shared();
402 parent_ = rhs.parent_;
407 using namespace detail;
408 if (parent_) releaseRead(parent_->mutex_);
411 const T* operator->() const {
412 return parent_ ? &parent_->datum_ : NULL;
415 struct Unsynchronizer {
416 explicit Unsynchronizer(ConstLockedPtr* p) : parent_(p) {
417 using namespace detail;
418 releaseRead(parent_->parent_->mutex_);
420 Unsynchronizer(const Unsynchronizer&) = delete;
421 Unsynchronizer& operator=(const Unsynchronizer&) = delete;
423 using namespace detail;
424 acquireRead(parent_->parent_->mutex_);
426 ConstLockedPtr* operator->() const {
430 ConstLockedPtr* parent_;
432 friend struct Unsynchronizer;
433 Unsynchronizer typeHackDoNotUse();
435 template <class P1, class P2>
436 friend void lockInOrder(P1& p1, P2& p2);
440 using namespace detail;
441 if (parent_) acquireRead(parent_->mutex_);
444 const Synchronized* parent_;
448 * This accessor offers a LockedPtr. In turn. LockedPtr offers
449 * operator-> returning a pointer to T. The operator-> keeps
450 * expanding until it reaches a pointer, so syncobj->foo() will lock
451 * the object and call foo() against it.
453 LockedPtr operator->() {
454 return LockedPtr(this);
458 * Same, for constant objects. You will be able to invoke only const
461 ConstLockedPtr operator->() const {
462 return ConstLockedPtr(this);
466 * Attempts to acquire for a given number of milliseconds. If
467 * acquisition is unsuccessful, the returned LockedPtr is NULL.
469 LockedPtr timedAcquire(unsigned int milliseconds) {
470 return LockedPtr(this, milliseconds);
474 * As above, for a constant object.
476 ConstLockedPtr timedAcquire(unsigned int milliseconds) const {
477 return ConstLockedPtr(this, milliseconds);
481 * Used by SYNCHRONIZED_DUAL.
483 LockedPtr internalDoNotUse() {
484 return LockedPtr(this, detail::InternalDoNotUse());
490 ConstLockedPtr internalDoNotUse() const {
491 return ConstLockedPtr(this, detail::InternalDoNotUse());
495 * Sometimes, although you have a mutable object, you only want to
496 * call a const method against it. The most efficient way to achieve
497 * that is by using a read lock. You get to do so by using
498 * obj.asConst()->method() instead of obj->method().
500 const Synchronized& asConst() const {
505 * Swaps with another Synchronized. Protected against
506 * self-swap. Only data is swapped. Locks are acquired in increasing
509 void swap(Synchronized& rhs) {
514 return rhs.swap(*this);
516 auto guard1 = operator->();
517 auto guard2 = rhs.operator->();
518 datum_.swap(rhs.datum_);
522 * Swap with another datum. Recommended because it keeps the mutex
526 LockedPtr guard = operator->();
531 * Copies datum to a given target.
533 void copy(T* target) const {
534 ConstLockedPtr guard = operator->();
539 * Returns a fresh copy of the datum.
542 ConstLockedPtr guard = operator->();
548 mutable Mutex mutex_;
551 // Non-member swap primitive
552 template <class T, class M>
553 void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) {
558 * SYNCHRONIZED is the main facility that makes Synchronized<T>
559 * helpful. It is a pseudo-statement that introduces a scope where the
560 * object is locked. Inside that scope you get to access the unadorned
565 * Synchronized<vector<int>> svector;
567 * SYNCHRONIZED (svector) { ... use svector as a vector<int> ... }
569 * SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... }
571 * Refer to folly/docs/Synchronized.md for a detailed explanation and more
574 #define SYNCHRONIZED(...) \
575 if (bool SYNCHRONIZED_state = false) {} else \
576 for (auto SYNCHRONIZED_lockedPtr = \
577 (FB_ARG_2_OR_1(__VA_ARGS__)).operator->(); \
578 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
579 for (auto& FB_ARG_1(__VA_ARGS__) = \
580 *SYNCHRONIZED_lockedPtr.operator->(); \
581 !SYNCHRONIZED_state; SYNCHRONIZED_state = true)
583 #define TIMED_SYNCHRONIZED(timeout, ...) \
584 if (bool SYNCHRONIZED_state = false) {} else \
585 for (auto SYNCHRONIZED_lockedPtr = \
586 (FB_ARG_2_OR_1(__VA_ARGS__)).timedAcquire(timeout); \
587 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
588 for (auto FB_ARG_1(__VA_ARGS__) = \
589 SYNCHRONIZED_lockedPtr.operator->(); \
590 !SYNCHRONIZED_state; SYNCHRONIZED_state = true)
593 * Similar to SYNCHRONIZED, but only uses a read lock.
595 #define SYNCHRONIZED_CONST(...) \
596 SYNCHRONIZED(FB_ARG_1(__VA_ARGS__), \
597 (FB_ARG_2_OR_1(__VA_ARGS__)).asConst())
600 * Similar to TIMED_SYNCHRONIZED, but only uses a read lock.
602 #define TIMED_SYNCHRONIZED_CONST(timeout, ...) \
603 TIMED_SYNCHRONIZED(timeout, FB_ARG_1(__VA_ARGS__), \
604 (FB_ARG_2_OR_1(__VA_ARGS__)).asConst())
607 * Temporarily disables synchronization inside a SYNCHRONIZED block.
609 #define UNSYNCHRONIZED(name) \
610 for (decltype(SYNCHRONIZED_lockedPtr.typeHackDoNotUse()) \
611 SYNCHRONIZED_state3(&SYNCHRONIZED_lockedPtr); \
612 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
613 for (auto name = *SYNCHRONIZED_state3.operator->(); \
614 !SYNCHRONIZED_state; SYNCHRONIZED_state = true)
617 * Locks two objects in increasing order of their addresses.
619 template <class P1, class P2>
620 void lockInOrder(P1& p1, P2& p2) {
621 if (static_cast<const void*>(p1.operator->()) >
622 static_cast<const void*>(p2.operator->())) {
632 * Synchronizes two Synchronized objects (they may encapsulate
633 * different data). Synchronization is done in increasing address of
634 * object order, so there is no deadlock risk.
636 #define SYNCHRONIZED_DUAL(n1, e1, n2, e2) \
637 if (bool SYNCHRONIZED_state = false) {} else \
638 for (auto SYNCHRONIZED_lp1 = (e1).internalDoNotUse(); \
639 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
640 for (auto& n1 = *SYNCHRONIZED_lp1.operator->(); \
641 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
642 for (auto SYNCHRONIZED_lp2 = (e2).internalDoNotUse(); \
643 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
644 for (auto& n2 = *SYNCHRONIZED_lp2.operator->(); \
645 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
646 if ((::folly::lockInOrder( \
647 SYNCHRONIZED_lp1, SYNCHRONIZED_lp2), \
651 } /* namespace folly */
653 #endif // SYNCHRONIZED_H_