Skip to content

Commit

Permalink
Fix issue that may cause deadlocks when using MCS lock with many threads
Browse files Browse the repository at this point in the history
  • Loading branch information
ch4rr0 committed Jun 30, 2021
1 parent 6041118 commit b4d6ed8
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 33 deletions.
20 changes: 10 additions & 10 deletions bt2_locks.cpp
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
#include "bt2_locks.h"

#if (__cplusplus >= 201103L)

void mcs_lock::lock() {
#include "bt2_locks.h"

void mcs_lock::lock(mcs_node &node) {
node.next = nullptr;
node.unlocked = false;

mcs_node *pred = q.exchange(&node, std::memory_order_release);
mcs_node *pred = q.exchange(&node, std::memory_order_acq_rel);
if (pred) {
pred->next = &node;
node.unlocked = false;
pred->next.store(&node, std::memory_order_release);
spin_while_eq(node.unlocked, false);
}
node.unlocked.load(std::memory_order_acquire);
}

void mcs_lock::unlock() {
if (!node.next) {
void mcs_lock::unlock(mcs_node &node) {
if (!node.next.load(std::memory_order_acquire)) {
mcs_node *node_ptr = &node;
if (q.compare_exchange_strong(node_ptr,
(mcs_node *)nullptr,
std::memory_order_release))
return;
spin_while_eq(node.next, (mcs_node *)nullptr);
}
node.next->unlocked.store(true, std::memory_order_release);
node.next.load(std::memory_order_acquire)->unlocked.store(true, std::memory_order_release);
}

thread_local mcs_lock::mcs_node mcs_lock::node;

void spin_lock::lock() {
cpu_backoff backoff;
Expand All @@ -37,4 +36,5 @@ void spin_lock::lock() {
void spin_lock::unlock() {
flag.clear(std::memory_order_release);
}

#endif
26 changes: 15 additions & 11 deletions bt2_locks.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@
class cpu_backoff {
public:
cpu_backoff(): count(1) {}
void pause() {
inline void pause() {
if (count <= LOOPS_BEFORE_YIELD) {
for (int32_t i = 0; i < count; i++) {
#ifdef __aarch64__
__asm__ __volatile__("yield" ::: "memory");
#else
#elif __ppc__
__asm__ __volatile__("or 27,27,27" ::: "memory");
#elif __x86_64__
__asm__ __volatile__("pause;");
#else
// do nothing
#endif
}
count *= 2;
Expand All @@ -44,27 +48,27 @@ class mcs_lock {
public:
mcs_lock(): q(nullptr) {}
struct mcs_node {
mcs_node *next;
std::atomic<mcs_node *> next;
std::atomic_bool unlocked;
};

void lock();
void unlock();
typedef mcs_node* mcs_node_ptr;
void lock(mcs_node &node);
void unlock(mcs_node &node);
typedef std::atomic<mcs_node *> mcs_node_ptr;
private:
void spin_while_eq(const volatile mcs_node_ptr& value, mcs_node *expected) {
void spin_while_eq(const volatile mcs_node_ptr& value, const volatile mcs_node *expected) {
cpu_backoff backoff;
while (value == expected)
while (value.load(std::memory_order_acquire) == expected)
backoff.pause();
}

void spin_while_eq(const volatile std::atomic_bool& value, bool expected) {
void spin_while_eq(const volatile std::atomic_bool& value, const volatile bool expected) {
cpu_backoff backoff;
while (value.load(std::memory_order_acquire) == expected)
backoff.pause();
}
std::atomic<mcs_node *> q;
static thread_local mcs_node node;
};
#endif

#endif // if (__cplusplus >= 201103L)
#endif // __MCS_LOCK_H__
60 changes: 48 additions & 12 deletions threading.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@
#ifdef NO_SPINLOCK
# if (__cplusplus >= 201103L)
# ifdef WITH_QUEUELOCK
#include "bt2_locks.h"
# define MUTEX_T mcs_lock
# include "bt2_locks.h"
# define MUTEX_T mcs_lock
# else
# define MUTEX_T std::mutex
# define MUTEX_T std::mutex
# endif
# else
# define MUTEX_T tthread::mutex
# ifdef WITH_QUEUELOCK
# error "QUEUELOCK requires C++11 or newer."
# else
# define MUTEX_T tthread::mutex
# endif
# endif
#else
# if (__cplusplus >= 201103L)
Expand Down Expand Up @@ -57,24 +61,56 @@ struct thread_tracking_pair {
/**
* Wrap a lock; obtain lock upon construction, release upon destruction.
*/
// class ThreadSafe {
// public:

// ThreadSafe(MUTEX_T* ptr_mutex) :
// ptr_mutex_(ptr_mutex)
// {
// assert(ptr_mutex_ != NULL);
// ptr_mutex_->lock();
// }

// ~ThreadSafe() {
// ptr_mutex_->unlock();
// }

// private:
// MUTEX_T *ptr_mutex_;
// };
class ThreadSafe {
public:

ThreadSafe(MUTEX_T* ptr_mutex) :
ptr_mutex_(ptr_mutex)
{
assert(ptr_mutex_ != NULL);
ptr_mutex_->lock();
}
ThreadSafe(MUTEX_T *mutex) :
#if __cplusplus >= 201103L && NO_SPINLOCK && WITH_QUEUELOCK
node_{},
#endif
mutex_(mutex) {
#if __cplusplus >= 201103L && NO_SPINLOCK && WITH_QUEUELOCK
mutex_->lock(node_);
#else
mutex_->lock();
#endif

}

~ThreadSafe() {
ptr_mutex_->unlock();
#if __cplusplus >= 201103L && NO_SPINLOCK && WITH_QUEUELOCK
mutex_->unlock(node_);
#else
mutex_->unlock();
#endif
}

private:
MUTEX_T *ptr_mutex_;

#if __cplusplus >= 201103L && NO_SPINLOCK && WITH_QUEUELOCK
MUTEX_T::mcs_node node_;
#endif
MUTEX_T *mutex_;
};


#if defined(_TTHREAD_WIN32_)
#define SLEEP(x) Sleep(x)
#else
Expand Down

0 comments on commit b4d6ed8

Please sign in to comment.