2 * Copyright 2014 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 boost::mutex, boost::recursive_mutex, boost::shared_mutex
52 #ifndef __APPLE__ // OSX doesn't have timed mutexes
53 ,std::timed_mutex, std::recursive_timed_mutex,
54 boost::timed_mutex, boost::recursive_timed_mutex
60 * Acquires a mutex for reading by calling .lock(). The exception is
61 * boost::shared_mutex, which has a special read-lock primitive called
65 typename std::enable_if<
66 HasLockUnlock<T>::value && !std::is_same<T, boost::shared_mutex>::value>::type
67 acquireRead(T& mutex) {
72 * Special case for boost::shared_mutex.
75 typename std::enable_if<std::is_same<T, boost::shared_mutex>::value>::type
76 acquireRead(T& mutex) {
81 * Acquires a mutex for reading with timeout by calling .timed_lock(). This
82 * applies to three of the boost mutex classes as enumerated below.
85 typename std::enable_if<std::is_same<T, boost::shared_mutex>::value, bool>::type
87 unsigned int milliseconds) {
88 return mutex.timed_lock_shared(boost::posix_time::milliseconds(milliseconds));
92 * Acquires a mutex for reading and writing by calling .lock().
95 typename std::enable_if<HasLockUnlock<T>::value>::type
96 acquireReadWrite(T& mutex) {
100 #ifndef __APPLE__ // OSX doesn't have timed mutexes
102 * Acquires a mutex for reading and writing with timeout by calling
103 * .try_lock_for(). This applies to two of the std mutex classes as
107 typename std::enable_if<
108 IsOneOf<T, std::timed_mutex, std::recursive_timed_mutex>::value, bool>::type
109 acquireReadWrite(T& mutex,
110 unsigned int milliseconds) {
111 // work around try_lock_for bug in some gcc versions, see
112 // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54562
113 return mutex.try_lock()
114 || (milliseconds > 0 &&
115 mutex.try_lock_until(std::chrono::system_clock::now() +
116 std::chrono::milliseconds(milliseconds)));
120 * Acquires a mutex for reading and writing with timeout by calling
121 * .timed_lock(). This applies to three of the boost mutex classes as
125 typename std::enable_if<
126 IsOneOf<T, boost::shared_mutex, boost::timed_mutex,
127 boost::recursive_timed_mutex>::value, bool>::type
128 acquireReadWrite(T& mutex,
129 unsigned int milliseconds) {
130 return mutex.timed_lock(boost::posix_time::milliseconds(milliseconds));
135 * Releases a mutex previously acquired for reading by calling
136 * .unlock(). The exception is boost::shared_mutex, which has a
137 * special primitive called .unlock_shared().
140 typename std::enable_if<
141 HasLockUnlock<T>::value && !std::is_same<T, boost::shared_mutex>::value>::type
142 releaseRead(T& mutex) {
147 * Special case for boost::shared_mutex.
150 typename std::enable_if<std::is_same<T, boost::shared_mutex>::value>::type
151 releaseRead(T& mutex) {
152 mutex.unlock_shared();
156 * Releases a mutex previously acquired for reading-writing by calling
160 typename std::enable_if<HasLockUnlock<T>::value>::type
161 releaseReadWrite(T& mutex) {
165 } // namespace detail
168 * Synchronized<T> encapsulates an object of type T (a "datum") paired
169 * with a mutex. The only way to access the datum is while the mutex
170 * is locked, and Synchronized makes it virtually impossible to do
171 * otherwise. The code that would access the datum in unsafe ways
172 * would look odd and convoluted, thus readily alerting the human
173 * reviewer. In contrast, the code that uses Synchronized<T> correctly
174 * looks simple and intuitive.
176 * The second parameter must be a mutex type. Supported mutexes are
177 * std::mutex, std::recursive_mutex, std::timed_mutex,
178 * std::recursive_timed_mutex, boost::mutex, boost::recursive_mutex,
179 * boost::shared_mutex, boost::timed_mutex,
180 * boost::recursive_timed_mutex, and the folly/RWSpinLock.h
183 * You may define Synchronized support by defining 4-6 primitives in
184 * the same namespace as the mutex class (found via ADL). The
185 * primitives are: acquireRead, acquireReadWrite, releaseRead, and
186 * releaseReadWrite. Two optional primitives for timout operations are
187 * overloads of acquireRead and acquireReadWrite. For signatures,
188 * refer to the namespace detail below, which implements the
189 * primitives for mutexes in std and boost.
191 template <class T, class Mutex = boost::shared_mutex>
192 struct Synchronized {
194 * Default constructor leaves both members call their own default
197 Synchronized() = default;
200 * Copy constructor copies the data (with locking the source and
201 * all) but does NOT copy the mutex. Doing so would result in
204 Synchronized(const Synchronized& rhs) {
205 auto guard = rhs.operator->();
210 * Move constructor moves the data (with locking the source and all)
211 * but does not move the mutex.
213 Synchronized(Synchronized&& rhs) {
214 auto guard = rhs.operator->();
215 datum_ = std::move(rhs.datum_);
219 * Constructor taking a datum as argument copies it. There is no
220 * need to lock the constructing object.
222 explicit Synchronized(const T& rhs) : datum_(rhs) {}
225 * Constructor taking a datum rvalue as argument moves it. Again,
226 * there is no need to lock the constructing object.
228 explicit Synchronized(T&& rhs) : datum_(std::move(rhs)) {}
231 * The canonical assignment operator only assigns the data, NOT the
232 * mutex. It locks the two objects in ascending order of their
235 Synchronized& operator=(const Synchronized& rhs) {
237 // Self-assignment, pass.
238 } else if (this < &rhs) {
239 auto guard1 = operator->();
240 auto guard2 = rhs.operator->();
243 auto guard1 = rhs.operator->();
244 auto guard2 = operator->();
251 * Move assignment operator, only assigns the data, NOT the
252 * mutex. It locks the two objects in ascending order of their
255 Synchronized& operator=(Synchronized&& rhs) {
257 // Self-assignment, pass.
258 } else if (this < &rhs) {
259 auto guard1 = operator->();
260 auto guard2 = rhs.operator->();
261 datum_ = std::move(rhs.datum_);
263 auto guard1 = rhs.operator->();
264 auto guard2 = operator->();
265 datum_ = std::move(rhs.datum_);
271 * Lock object, assign datum.
273 Synchronized& operator=(const T& rhs) {
274 auto guard = operator->();
280 * Lock object, move-assign datum.
282 Synchronized& operator=(T&& rhs) {
283 auto guard = operator->();
284 datum_ = std::move(rhs);
289 * A LockedPtr lp keeps a modifiable (i.e. non-const)
290 * Synchronized<T> object locked for the duration of lp's
291 * existence. Because of this, you get to access the datum's methods
292 * directly by using lp->fun().
296 * Found no reason to leave this hanging.
298 LockedPtr() = delete;
301 * Takes a Synchronized and locks it.
303 explicit LockedPtr(Synchronized* parent) : parent_(parent) {
308 * Takes a Synchronized and attempts to lock it for some
309 * milliseconds. If not, the LockedPtr will be subsequently null.
311 LockedPtr(Synchronized* parent, unsigned int milliseconds) {
312 using namespace detail;
313 if (acquireReadWrite(parent->mutex_, milliseconds)) {
317 // Could not acquire the resource, pointer is null
322 * This is used ONLY inside SYNCHRONIZED_DUAL. It initializes
323 * everything properly, but does not lock the parent because it
324 * "knows" someone else will lock it. Please do not use.
326 LockedPtr(Synchronized* parent, detail::InternalDoNotUse)
331 * Copy ctor adds one lock.
333 LockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) {
338 * Assigning from another LockedPtr results in freeing the former
339 * lock and acquiring the new one. The method works with
340 * self-assignment (does nothing).
342 LockedPtr& operator=(const LockedPtr& rhs) {
343 if (parent_ != rhs.parent_) {
344 if (parent_) parent_->mutex_.unlock();
345 parent_ = rhs.parent_;
352 * Destructor releases.
355 using namespace detail;
356 if (parent_) releaseReadWrite(parent_->mutex_);
360 * Safe to access the data. Don't save the obtained pointer by
361 * invoking lp.operator->() by hand. Also, if the method returns a
362 * handle stored inside the datum, don't use this idiom - use
363 * SYNCHRONIZED below.
366 return parent_ ? &parent_->datum_ : nullptr;
370 * This class temporarily unlocks a LockedPtr in a scoped
371 * manner. It is used inside of the UNSYNCHRONIZED macro.
373 struct Unsynchronizer {
374 explicit Unsynchronizer(LockedPtr* p) : parent_(p) {
375 using namespace detail;
376 releaseReadWrite(parent_->parent_->mutex_);
378 Unsynchronizer(const Unsynchronizer&) = delete;
379 Unsynchronizer& operator=(const Unsynchronizer&) = delete;
383 LockedPtr* operator->() const {
389 friend struct Unsynchronizer;
390 Unsynchronizer typeHackDoNotUse();
392 template <class P1, class P2>
393 friend void lockInOrder(P1& p1, P2& p2);
397 using namespace detail;
398 if (parent_) acquireReadWrite(parent_->mutex_);
401 // This is the entire state of LockedPtr.
402 Synchronized* parent_;
406 * ConstLockedPtr does exactly what LockedPtr does, but for const
407 * Synchronized objects. Of interest is that ConstLockedPtr only
408 * uses a read lock, which is faster but more restrictive - you only
409 * get to call const methods of the datum.
411 * Much of the code between LockedPtr and
412 * ConstLockedPtr is identical and could be factor out, but there
413 * are enough nagging little differences to not justify the trouble.
415 struct ConstLockedPtr {
416 ConstLockedPtr() = delete;
417 explicit ConstLockedPtr(const Synchronized* parent) : parent_(parent) {
420 ConstLockedPtr(const Synchronized* parent, detail::InternalDoNotUse)
423 ConstLockedPtr(const ConstLockedPtr& rhs) : parent_(rhs.parent_) {
426 explicit ConstLockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) {
429 ConstLockedPtr(const Synchronized* parent, unsigned int milliseconds) {
430 if (parent->mutex_.timed_lock_shared(
431 boost::posix_time::milliseconds(milliseconds))) {
435 // Could not acquire the resource, pointer is null
439 ConstLockedPtr& operator=(const ConstLockedPtr& rhs) {
440 if (parent_ != rhs.parent_) {
441 if (parent_) parent_->mutex_.unlock_shared();
442 parent_ = rhs.parent_;
447 using namespace detail;
448 if (parent_) releaseRead(parent_->mutex_);
451 const T* operator->() const {
452 return parent_ ? &parent_->datum_ : nullptr;
455 struct Unsynchronizer {
456 explicit Unsynchronizer(ConstLockedPtr* p) : parent_(p) {
457 using namespace detail;
458 releaseRead(parent_->parent_->mutex_);
460 Unsynchronizer(const Unsynchronizer&) = delete;
461 Unsynchronizer& operator=(const Unsynchronizer&) = delete;
463 using namespace detail;
464 acquireRead(parent_->parent_->mutex_);
466 ConstLockedPtr* operator->() const {
470 ConstLockedPtr* parent_;
472 friend struct Unsynchronizer;
473 Unsynchronizer typeHackDoNotUse();
475 template <class P1, class P2>
476 friend void lockInOrder(P1& p1, P2& p2);
480 using namespace detail;
481 if (parent_) acquireRead(parent_->mutex_);
484 const Synchronized* parent_;
488 * This accessor offers a LockedPtr. In turn. LockedPtr offers
489 * operator-> returning a pointer to T. The operator-> keeps
490 * expanding until it reaches a pointer, so syncobj->foo() will lock
491 * the object and call foo() against it.
493 LockedPtr operator->() {
494 return LockedPtr(this);
498 * Same, for constant objects. You will be able to invoke only const
501 ConstLockedPtr operator->() const {
502 return ConstLockedPtr(this);
506 * Attempts to acquire for a given number of milliseconds. If
507 * acquisition is unsuccessful, the returned LockedPtr is NULL.
509 LockedPtr timedAcquire(unsigned int milliseconds) {
510 return LockedPtr(this, milliseconds);
514 * As above, for a constant object.
516 ConstLockedPtr timedAcquire(unsigned int milliseconds) const {
517 return ConstLockedPtr(this, milliseconds);
521 * Used by SYNCHRONIZED_DUAL.
523 LockedPtr internalDoNotUse() {
524 return LockedPtr(this, detail::InternalDoNotUse());
530 ConstLockedPtr internalDoNotUse() const {
531 return ConstLockedPtr(this, detail::InternalDoNotUse());
535 * Sometimes, although you have a mutable object, you only want to
536 * call a const method against it. The most efficient way to achieve
537 * that is by using a read lock. You get to do so by using
538 * obj.asConst()->method() instead of obj->method().
540 const Synchronized& asConst() const {
545 * Swaps with another Synchronized. Protected against
546 * self-swap. Only data is swapped. Locks are acquired in increasing
549 void swap(Synchronized& rhs) {
554 return rhs.swap(*this);
556 auto guard1 = operator->();
557 auto guard2 = rhs.operator->();
560 swap(datum_, rhs.datum_);
564 * Swap with another datum. Recommended because it keeps the mutex
568 LockedPtr guard = operator->();
575 * Copies datum to a given target.
577 void copy(T* target) const {
578 ConstLockedPtr guard = operator->();
583 * Returns a fresh copy of the datum.
586 ConstLockedPtr guard = operator->();
592 mutable Mutex mutex_;
595 // Non-member swap primitive
596 template <class T, class M>
597 void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) {
602 * SYNCHRONIZED is the main facility that makes Synchronized<T>
603 * helpful. It is a pseudo-statement that introduces a scope where the
604 * object is locked. Inside that scope you get to access the unadorned
609 * Synchronized<vector<int>> svector;
611 * SYNCHRONIZED (svector) { ... use svector as a vector<int> ... }
613 * SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... }
615 * Refer to folly/docs/Synchronized.md for a detailed explanation and more
618 #define SYNCHRONIZED(...) \
619 if (bool SYNCHRONIZED_state = false) {} else \
620 for (auto SYNCHRONIZED_lockedPtr = \
621 (FB_ARG_2_OR_1(__VA_ARGS__)).operator->(); \
622 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
623 for (auto& FB_ARG_1(__VA_ARGS__) = \
624 *SYNCHRONIZED_lockedPtr.operator->(); \
625 !SYNCHRONIZED_state; SYNCHRONIZED_state = true)
627 #define TIMED_SYNCHRONIZED(timeout, ...) \
628 if (bool SYNCHRONIZED_state = false) {} else \
629 for (auto SYNCHRONIZED_lockedPtr = \
630 (FB_ARG_2_OR_1(__VA_ARGS__)).timedAcquire(timeout); \
631 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
632 for (auto FB_ARG_1(__VA_ARGS__) = \
633 SYNCHRONIZED_lockedPtr.operator->(); \
634 !SYNCHRONIZED_state; SYNCHRONIZED_state = true)
637 * Similar to SYNCHRONIZED, but only uses a read lock.
639 #define SYNCHRONIZED_CONST(...) \
640 SYNCHRONIZED(FB_ARG_1(__VA_ARGS__), \
641 (FB_ARG_2_OR_1(__VA_ARGS__)).asConst())
644 * Similar to TIMED_SYNCHRONIZED, but only uses a read lock.
646 #define TIMED_SYNCHRONIZED_CONST(timeout, ...) \
647 TIMED_SYNCHRONIZED(timeout, FB_ARG_1(__VA_ARGS__), \
648 (FB_ARG_2_OR_1(__VA_ARGS__)).asConst())
651 * Temporarily disables synchronization inside a SYNCHRONIZED block.
653 #define UNSYNCHRONIZED(name) \
654 for (decltype(SYNCHRONIZED_lockedPtr.typeHackDoNotUse()) \
655 SYNCHRONIZED_state3(&SYNCHRONIZED_lockedPtr); \
656 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
657 for (auto name = *SYNCHRONIZED_state3.operator->(); \
658 !SYNCHRONIZED_state; SYNCHRONIZED_state = true)
661 * Locks two objects in increasing order of their addresses.
663 template <class P1, class P2>
664 void lockInOrder(P1& p1, P2& p2) {
665 if (static_cast<const void*>(p1.operator->()) >
666 static_cast<const void*>(p2.operator->())) {
676 * Synchronizes two Synchronized objects (they may encapsulate
677 * different data). Synchronization is done in increasing address of
678 * object order, so there is no deadlock risk.
680 #define SYNCHRONIZED_DUAL(n1, e1, n2, e2) \
681 if (bool SYNCHRONIZED_state = false) {} else \
682 for (auto SYNCHRONIZED_lp1 = (e1).internalDoNotUse(); \
683 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
684 for (auto& n1 = *SYNCHRONIZED_lp1.operator->(); \
685 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
686 for (auto SYNCHRONIZED_lp2 = (e2).internalDoNotUse(); \
687 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
688 for (auto& n2 = *SYNCHRONIZED_lp2.operator->(); \
689 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
690 if ((::folly::lockInOrder( \
691 SYNCHRONIZED_lp1, SYNCHRONIZED_lp2), \
695 } /* namespace folly */
697 #endif // SYNCHRONIZED_H_