Commit 36502346 authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook Github Bot

Let Sychronized move-constructor not lock the source

Summary: [Folly] Let `Sychronized` move-constructor not lock the source object, since it is an rvalue-reference and the move-constructor code may therefore be assumed to have the only live reference to the source. Same with the move-assignment operator.

Reviewed By: aary

Differential Revision: D8219883

fbshipit-source-id: f62ff87197ac4b9ceed290a73a05062ab8ed45c4
parent 8acd0060
...@@ -523,33 +523,36 @@ struct Synchronized : public SynchronizedBase< ...@@ -523,33 +523,36 @@ struct Synchronized : public SynchronizedBase<
*/ */
Synchronized() = default; Synchronized() = default;
public:
/** /**
* Copy constructor copies the data (with locking the source and * Copy constructor; deprecated
* all) but does NOT copy the mutex. Doing so would result in *
* deadlocks. * Enabled only when the data type is copy-constructible.
*
* Takes a shared-or-exclusive lock on the source mutex while performing the
* copy-construction of the destination data from the source data. No lock is
* taken on the destination mutex.
* *
* Note that the copy constructor may throw because it acquires a lock in * May throw even when the data type is is nothrow-copy-constructible because
* the contextualRLock() method * acquiring a lock may throw.
*/ */
public:
/* implicit */ Synchronized(typename std::conditional< /* implicit */ Synchronized(typename std::conditional<
std::is_copy_constructible<T>::value, std::is_copy_constructible<T>::value,
const Synchronized&, const Synchronized&,
NonImplementedType>::type rhs) /* may throw */ NonImplementedType>::type rhs) /* may throw */
: Synchronized(rhs, rhs.contextualRLock()) {} : Synchronized(rhs.copy()) {}
/** /**
* Move constructor moves the data (with locking the source and all) * Move constructor; deprecated
* but does not move the mutex. *
* Move-constructs from the source data without locking either the source or
* the destination mutex.
* *
* Note that the move constructor may throw because it acquires a lock. * Semantically, assumes that the source object is a true rvalue and therefore
* Since the move constructor is not declared noexcept, when objects of this * that no synchronization is required for accessing it.
* class are used as elements in a vector or a similar container. The
* elements might not be moved around when resizing. They might be copied
* instead. You have been warned.
*/ */
Synchronized(Synchronized&& rhs) /* may throw */ Synchronized(Synchronized&& rhs) noexcept(nxMoveCtor)
: Synchronized(std::move(rhs), rhs.contextualLock()) {} : Synchronized(std::move(rhs.datum_)) {}
/** /**
* Constructor taking a datum as argument copies it. There is no * Constructor taking a datum as argument copies it. There is no
...@@ -588,54 +591,49 @@ struct Synchronized : public SynchronizedBase< ...@@ -588,54 +591,49 @@ struct Synchronized : public SynchronizedBase<
make_index_sequence<sizeof...(MutexArgs)>{}} {} make_index_sequence<sizeof...(MutexArgs)>{}} {}
/** /**
* The canonical assignment operator only assigns the data, NOT the * Copy assignment operator; deprecated
* mutex. It locks the two objects in ascending order of their *
* addresses. * Enabled only when the data type is copy-constructible and move-assignable.
*
* Move-assigns from a copy of the source data.
*
* Takes a shared-or-exclusive lock on the source mutex while copying the
* source data to a temporary. Takes an exclusive lock on the destination
* mutex while move-assigning from the temporary.
*
* This technique consts an extra temporary but avoids the need to take locks
* on both mutexes together.
*/ */
Synchronized& operator=(typename std::conditional< Synchronized& operator=(typename std::conditional<
std::is_copy_assignable<T>::value, std::is_copy_constructible<T>::value &&
std::is_move_assignable<T>::value,
const Synchronized&, const Synchronized&,
NonImplementedType>::type rhs) { NonImplementedType>::type rhs) {
if (this == &rhs) { return *this = rhs.copy();
// Self-assignment, pass.
} else if (this < &rhs) {
auto guard1 = operator->();
auto guard2 = rhs.operator->();
datum_ = rhs.datum_;
} else {
auto guard1 = rhs.operator->();
auto guard2 = operator->();
datum_ = rhs.datum_;
}
return *this;
} }
/** /**
* Move assignment operator, only assigns the data, NOT the * Move assignment operator; deprecated
* mutex. It locks the two objects in ascending order of their *
* addresses. * Takes an exclusive lock on the destination mutex while move-assigning the
* destination data from the source data. The source mutex is not locked or
* otherwise accessed.
*
* Semantically, assumes that the source object is a true rvalue and therefore
* that no synchronization is required for accessing it.
*/ */
Synchronized& operator=(Synchronized&& rhs) { Synchronized& operator=(Synchronized&& rhs) {
if (this == &rhs) { return *this = std::move(rhs.datum_);
// Self-assignment, pass.
} else if (this < &rhs) {
auto guard1 = operator->();
auto guard2 = rhs.operator->();
datum_ = std::move(rhs.datum_);
} else {
auto guard1 = rhs.operator->();
auto guard2 = operator->();
datum_ = std::move(rhs.datum_);
}
return *this;
} }
/** /**
* Lock object, assign datum. * Lock object, assign datum.
*/ */
Synchronized& operator=(const T& rhs) { Synchronized& operator=(const T& rhs) {
auto guard = operator->(); if (&datum_ != &rhs) {
datum_ = rhs; auto guard = operator->();
datum_ = rhs;
}
return *this; return *this;
} }
...@@ -643,8 +641,10 @@ struct Synchronized : public SynchronizedBase< ...@@ -643,8 +641,10 @@ struct Synchronized : public SynchronizedBase<
* Lock object, move-assign datum. * Lock object, move-assign datum.
*/ */
Synchronized& operator=(T&& rhs) { Synchronized& operator=(T&& rhs) {
auto guard = operator->(); if (&datum_ != &rhs) {
datum_ = std::move(rhs); auto guard = operator->();
datum_ = std::move(rhs);
}
return *this; return *this;
} }
......
...@@ -198,27 +198,26 @@ takes an object of type `T` and copies it. For example: ...@@ -198,27 +198,26 @@ takes an object of type `T` and copies it. For example:
#### Assignment, swap, and copying #### Assignment, swap, and copying
The canonical assignment operator locks both objects involved and The copy assignment operator copies the underlying source data
then copies the underlying data objects. The mutexes are not into a temporary with the source mutex locked, and then move the
copied. The locks are acquired in increasing address order, so temporary into the destination data with the destination mutex
deadlock is avoided. For example, there is no problem if one locked. This technique avoids the need to lock both mutexes at
thread assigns `a = b` and the other assigns `b = a` (other than the same time. Mutexes are not copied or moved.
that design probably deserving a Razzie award). Similarly, the
`swap` method takes a reference to another `Synchronized<T>` The move assignment operator assumes the source object is a true
object and swaps the data. Again, locks are acquired in a well- rvalue and does lock lock the source mutex. It moves the source
defined order. The mutexes are not swapped. data into the destination data with the destination mutex locked.
An additional assignment operator accepts a `const T&` on the `swap` acquires locks on both mutexes in increasing order of
right-hand side. The operator copies the datum inside a object address, and then swaps the underlying data. This avoids
critical section. potential deadlock, which may otherwise happen should one thread
do `a = b` while another thread does `b = a`.
In addition to assignment operators, `Synchronized<T>` has move
assignment operators. The data copy assignment operator copies the parameter into the
destination data while the destination mutex is locked.
An additional `swap` method accepts a `T&` and swaps the data
inside a critical section. This is by far the preferred method of The data move assignment operator moves the parameter into the
changing the guarded datum wholesale because it keeps the lock destination data while the destination mutex is locked.
only for a short time, thus lowering the pressure on the mutex.
To get a copy of the guarded data, there are two methods To get a copy of the guarded data, there are two methods
available: `void copy(T*)` and `T copy()`. The first copies data available: `void copy(T*)` and `T copy()`. The first copies data
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment