[libcxx-commits] [libcxx] [libc++] add shared_recursive_mutex api (PR #82466)

via libcxx-commits libcxx-commits at lists.llvm.org
Tue Feb 20 23:07:28 PST 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libcxx

Author: Jolyon (Jolyon0202)

<details>
<summary>Changes</summary>

libc++'s shared_mutex favor writers over readers, but libstdc++'s shared_mutex favor readers over writers defaultly.

libstdc++¡®s shared_mutex use ptread_rwlock_t(PTHREAD_RWLOCK_PREFER_READER_NP defaultly), and it also support to favor writers over readers.

Official C++ Standard doesn't specify the std::shared_mutex policy. Explanation could be found in original N2406 proposal (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2406.html#shared_mutex_imp) in shared_mutex.

And we add shared_recursive_mutex api that favor readers over writers, and support Write-read nesting and write nesting.

---
Full diff: https://github.com/llvm/llvm-project/pull/82466.diff


5 Files Affected:

- (modified) libcxx/CMakeLists.txt (+2) 
- (modified) libcxx/include/CMakeLists.txt (+6) 
- (added) libcxx/include/shared_recursive_mutex (+61) 
- (modified) libcxx/src/CMakeLists.txt (+6) 
- (added) libcxx/src/shared_recursive_mutex.cpp (+126) 


``````````diff
diff --git a/libcxx/CMakeLists.txt b/libcxx/CMakeLists.txt
index d392e95077ac57..153456c59ee37d 100644
--- a/libcxx/CMakeLists.txt
+++ b/libcxx/CMakeLists.txt
@@ -308,6 +308,8 @@ else()
   set(LIBCXX_PSTL_CPU_BACKEND "serial" CACHE STRING "Which PSTL CPU backend to use")
 endif()
 
+option(LIBCXX_ENABLE_SHARED_RECURSIVE_MUTEX "Build libc++ with shared_recursive_mutex API" OFF)
+
 # Misc options ----------------------------------------------------------------
 # FIXME: Turn -pedantic back ON. It is currently off because it warns
 # about #include_next which is used everywhere.
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index cafd8c6e00d968..417c179810d62a 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -1021,6 +1021,12 @@ set(files
   wctype.h
   )
 
+if (LIBCXX_ENABLE_SHARED_RECURSIVE_MUTEX)
+  list(APPEND files
+    shared_recursive_mutex
+    )
+endif()
+
 foreach(feature LIBCXX_ENABLE_FILESYSTEM LIBCXX_ENABLE_LOCALIZATION LIBCXX_ENABLE_THREADS LIBCXX_ENABLE_WIDE_CHARACTERS)
   if (NOT ${${feature}})
     set(requires_${feature} "requires LIBCXX_CONFIGURED_WITHOUT_SUPPORT_FOR_THIS_HEADER")
diff --git a/libcxx/include/shared_recursive_mutex b/libcxx/include/shared_recursive_mutex
new file mode 100644
index 00000000000000..526ec531107a2f
--- /dev/null
+++ b/libcxx/include/shared_recursive_mutex
@@ -0,0 +1,61 @@
+#ifndef _SHARED_RECURSIVE_MUTEX_
+#define _SHARED_RECURSIVE_MUTEX_
+
+#include <cstdint>
+#include <mutex>
+#include <condition_variable>
+#include <thread>
+#include <shared_mutex>
+
+namespace std {
+/* Provides recurseve shared_mutex.
+ *
+ * 1. Function
+ *   |         type           |   policy       | Read Nesting | Read-write Nesting | Write-read Nesting | Write Nesting |
+ *   |  pthread_rwlock(glibc) |  Reader favor  |       Y      |      deadlock      |    return false    | return false  |
+ *   |  shared_mutex(gcc)     |  Reader favor  |       Y      |      deadlock      |       assert       |    assert     |
+ *   |  shared_mutex(llvm)    |  Write favor   |maybe deadlock|      deadlock      |      deadlock      |    deadlock   |
+ *   | shared_recursive_mutex |  Reader favor  |       Y      |      deadlock      |          Y         |       Y       |
+ *   Reasons for not supporting read/write nesting:
+ *     - The information about the read lock holder needs to be maintained, which affects the memory and performance.
+ *     - does not meet the contract of the read lock (no modification should be made during the hold period)
+ *
+ * 2. shared_mutex vs shared_recursive_mutex(SR) performance comparison
+ *   |       testcase       |  GCC RL  | GCC WL   | LLVM RL  |  LLVM WL |   SR RL  |   SR WL  |
+ *   |     1read 0write     | 7445029  |       0  | 3452907  |       0  | 3547000  |       0  |
+ *   |     0read 1write     |       0  | 5102869  |       0  | 7445029  |       0  | 3472935  |
+ *   |     1read 1write     |   47861  |   50153  |   31417  |  524955  |  224395  |  244908  |
+ *   |     2read 1write     |  444764  |   26655  |  120715  |  387924  |  397377  |  155332  |
+ *   |     4read 1write     | 1767244  |    9418  |  110139  |  214612  |  383453  |   21677  |
+ *   |     2read 2write     |  433766  |   25975  |   48125  |  312586  |  212341  |  117929  |
+ *   - base on ARMv7 8*A15
+ *   - The value is the number of lock operations performed by all threads in 10s. Larger is better.
+ *
+ * 3. shared_recursive_mutex support shared_lock and unique_lock.
+ */
+class shared_recursive_mutex {
+public:
+    shared_recursive_mutex() = default;
+    ~shared_recursive_mutex() = default;
+
+    shared_recursive_mutex(const shared_recursive_mutex &) = delete;
+    shared_recursive_mutex &operator = (const shared_recursive_mutex &) = delete;
+
+    void lock();
+    bool try_lock();
+    void unlock();
+
+    void lock_shared();
+    bool try_lock_shared();
+    void unlock_shared();
+
+private:
+    std::mutex mutex;
+    std::condition_variable cond;
+    pthread_t owner = 0;     // write owner.
+    uint32_t recursions = 0; // read lock recursions
+    uint32_t readers = 0;
+};
+}
+
+#endif
diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt
index cc6954a7bac37e..d68f8f5a29d9d7 100644
--- a/libcxx/src/CMakeLists.txt
+++ b/libcxx/src/CMakeLists.txt
@@ -62,6 +62,12 @@ set(LIBCXX_SOURCES
   verbose_abort.cpp
   )
 
+if (LIBCXX_ENABLE_SHARED_RECURSIVE_MUTEX)
+  list(APPEND LIBCXX_SOURCES
+    shared_recursive_mutex.cpp
+    )
+endif()
+
 if (LIBCXX_ENABLE_THREADS)
   list(APPEND LIBCXX_SOURCES
     atomic.cpp
diff --git a/libcxx/src/shared_recursive_mutex.cpp b/libcxx/src/shared_recursive_mutex.cpp
new file mode 100644
index 00000000000000..93cf5604b6946c
--- /dev/null
+++ b/libcxx/src/shared_recursive_mutex.cpp
@@ -0,0 +1,126 @@
+#include <shared_recursive_mutex>
+#include <cassert>
+
+constexpr uint32_t SHARED_USER_MAX_CNT = UINT32_MAX;
+constexpr pthread_t THREAD_ID_NOEXIST = 0;
+
+using namespace std;
+
+#ifndef likely
+#define likely(condition) __builtin_expect(!!(condition), 1)
+#endif
+#ifndef unlikely
+#define unlikely(condition)  __builtin_expect(!!(condition), 0)
+#endif
+
+void shared_recursive_mutex::lock()
+{
+    pthread_t self = pthread_self();
+    std::unique_lock guard(mutex);
+
+    // write lock reentrant
+    if (owner == self) {
+        if (unlikely(recursions == SHARED_USER_MAX_CNT)) {
+            return;
+        }
+        ++recursions;
+        return;
+    }
+
+    while ((owner != THREAD_ID_NOEXIST) || (readers != 0)) {
+        cond.wait(guard);
+    }
+
+    owner = self;
+    recursions = 1;
+}
+
+bool shared_recursive_mutex::try_lock()
+{
+    pthread_t self = pthread_self();
+    std::lock_guard guard(mutex);
+
+    // write lock reentrant
+    if (owner == self) {
+        if (unlikely(recursions == SHARED_USER_MAX_CNT)) {
+            return false;
+        }
+        ++recursions;
+        return true;
+    }
+
+    if ((owner != THREAD_ID_NOEXIST) || (readers != 0)) {
+        return false;
+    }
+
+    owner = self;
+    recursions = 1;
+
+    return true;
+}
+
+void shared_recursive_mutex::unlock()
+{
+    pthread_t self = pthread_self();
+    std::lock_guard guard(mutex);
+
+    if (unlikely((owner != self) || (recursions == 0))) {
+        return;
+    }
+
+    if (--recursions == 0) {
+        owner = 0;
+        // release write lock and notifies all the servers.
+        cond.notify_all();
+    }
+}
+
+void shared_recursive_mutex::lock_shared() {
+    pthread_t self = pthread_self();
+    std::unique_lock guard(mutex);
+
+    // write-read nesting
+    if (owner == self) {
+        ++readers;
+        return;
+    }
+
+    // If other threads have held the write lock or the number of read locks exceeds the upper limit, wait.
+    while (unlikely(owner != THREAD_ID_NOEXIST) || unlikely(readers == SHARED_USER_MAX_CNT)) {
+        cond.wait(guard);
+    }
+
+    ++readers;
+}
+
+bool shared_recursive_mutex::try_lock_shared()
+{
+    pthread_t self = pthread_self();
+    std::lock_guard guard(mutex);
+
+    // write-read nesting
+    if (owner == self) {
+        ++readers;
+        return true;
+    }
+
+    // If another thread already holds the write lock or the number of read locks exceeds the upper limit, the operation fails.
+    if (unlikely(owner != THREAD_ID_NOEXIST) || unlikely(readers == SHARED_USER_MAX_CNT)) {
+        return false;
+    }
+
+    ++readers;
+    return true;
+}
+
+void shared_recursive_mutex::unlock_shared()
+{
+    std::lock_guard guard(mutex);
+
+    if (readers == 0) {
+        return;
+    }
+
+    --readers;
+    cond.notify_all();
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/82466


More information about the libcxx-commits mailing list