[libcxx-commits] [libcxx] [libc++] add shared_recursive_mutex api (PR #82466)
via libcxx-commits
libcxx-commits at lists.llvm.org
Sat Mar 16 22:19:44 PDT 2024
https://github.com/Jolyon0202 updated https://github.com/llvm/llvm-project/pull/82466
>From 9eb531c6b7169905384fe7e24463847cb15465d2 Mon Sep 17 00:00:00 2001
From: Jian Yang <yangjian86 at huawei.com>
Date: Sun, 17 Mar 2024 13:14:48 +0800
Subject: [PATCH] [libc++] add shared_recursive_mutex api (#80000)
libc++'s shared_mutex favor writers over readers, but libstdc++'s shared_mut
ex 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. Explanat
ion 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 s
upport Write-read nesting and write nesting.
---
libcxx/CMakeLists.txt | 2 +
libcxx/include/CMakeLists.txt | 6 ++
libcxx/include/shared_recursive_mutex | 71 ++++++++++++++++
libcxx/src/CMakeLists.txt | 6 ++
libcxx/src/shared_recursive_mutex.cpp | 114 ++++++++++++++++++++++++++
5 files changed, 199 insertions(+)
create mode 100644 libcxx/include/shared_recursive_mutex
create mode 100644 libcxx/src/shared_recursive_mutex.cpp
diff --git a/libcxx/CMakeLists.txt b/libcxx/CMakeLists.txt
index e565c47c76687a..eb4555bd45e735 100644
--- a/libcxx/CMakeLists.txt
+++ b/libcxx/CMakeLists.txt
@@ -306,6 +306,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 63adc03fae2980..a3342992cef15c 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -1023,6 +1023,12 @@ set(files
wctype.h
)
+if (LIBCXX_ENABLE_SHARED_RECURSIVE_MUTEX)
+ list(APPEND files
+ shared_recursive_mutex
+ )
+endif()
+
configure_file("__config_site.in" "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/__config_site" @ONLY)
configure_file("${LIBCXX_ASSERTION_HANDLER_FILE}" "${LIBCXX_GENERATED_INCLUDE_DIR}/__assertion_handler" COPYONLY)
diff --git a/libcxx/include/shared_recursive_mutex b/libcxx/include/shared_recursive_mutex
new file mode 100644
index 00000000000000..bfa687061777b2
--- /dev/null
+++ b/libcxx/include/shared_recursive_mutex
@@ -0,0 +1,71 @@
+#ifndef _SHARED_RECURSIVE_MUTEX_
+#define _SHARED_RECURSIVE_MUTEX_
+
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+#include <cstdint>
+
+#ifdef _LIBCPP_HAS_NO_THREADS
+# error "<shared_recursive_mutex> is not supported since libc++ has been configured without support for threads."
+#endif
+
+_LIBCPP_BEGIN_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.(shared_lock should include shared_mutex header)
+ */
+
+namespace __shared_recursive_mutex {
+
+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_;
+ std::__thread_id __owner_ = std::__thread_id(); // write owner.
+ uint32_t __recursions_ = 0; // read lock recursions
+ uint32_t __readers_ = 0;
+};
+
+} // namespace __shared_recursive_mutex
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif
diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt
index 1110a79ddcacd5..54eda0448e76fb 100644
--- a/libcxx/src/CMakeLists.txt
+++ b/libcxx/src/CMakeLists.txt
@@ -76,6 +76,12 @@ if (LIBCXX_ENABLE_THREADS)
)
endif()
+if (LIBCXX_ENABLE_SHARED_RECURSIVE_MUTEX)
+ list(APPEND LIBCXX_SOURCES
+ shared_recursive_mutex.cpp
+ )
+endif()
+
if (LIBCXX_ENABLE_RANDOM_DEVICE)
list(APPEND LIBCXX_SOURCES
random.cpp
diff --git a/libcxx/src/shared_recursive_mutex.cpp b/libcxx/src/shared_recursive_mutex.cpp
new file mode 100644
index 00000000000000..e4e08fe8f09b5e
--- /dev/null
+++ b/libcxx/src/shared_recursive_mutex.cpp
@@ -0,0 +1,114 @@
+#include <__threading_support>
+#include <shared_recursive_mutex>
+
+constexpr uint32_t SHARED_USER_MAX_CNT = UINT32_MAX;
+std::__thread_id THREAD_ID_NOEXIST = std::__thread_id();
+
+using namespace std::__shared_recursive_mutex;
+
+void shared_recursive_mutex::lock() {
+ std::__thread_id self = std::this_thread::get_id();
+ std::unique_lock guard(__mutex_);
+
+ // write lock reentrant
+ if (__owner_ == self) {
+ if (__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() {
+ std::__thread_id self = std::this_thread::get_id();
+ std::lock_guard guard(__mutex_);
+
+ // write lock reentrant
+ if (__owner_ == self) {
+ if (__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() {
+ std::__thread_id self = std::this_thread::get_id();
+ std::lock_guard guard(__mutex_);
+
+ if ((__owner_ != self) || (__recursions_ == 0)) {
+ return;
+ }
+
+ if (--__recursions_ == 0) {
+ __owner_.__reset();
+ // release write lock and notifies all the servers.
+ __cond_.notify_all();
+ }
+}
+
+void shared_recursive_mutex::lock_shared() {
+ std::__thread_id self = std::this_thread::get_id();
+ 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 (__owner_ != THREAD_ID_NOEXIST || __readers_ == SHARED_USER_MAX_CNT) {
+ __cond_.wait(guard);
+ }
+
+ ++__readers_;
+}
+
+bool shared_recursive_mutex::try_lock_shared() {
+ std::__thread_id self = std::this_thread::get_id();
+ 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 (__owner_ != THREAD_ID_NOEXIST || __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();
+}
More information about the libcxx-commits
mailing list