From 91e96b17b292e72a3a4b733c1faa43b405059bc0 Mon Sep 17 00:00:00 2001 From: tqcq Date: Wed, 13 Dec 2023 12:56:40 +0800 Subject: [PATCH] feature add Mutex,ConditionVariable,Event,MutexGuard --- .gitea/workflows/linux-x64-gcc.yml | 23 +++---- CMakeLists.txt | 15 ++++- src/ulib/concorrency/condition_variable.cpp | 38 +++++++++++ src/ulib/concorrency/condition_variable.h | 47 ++++++++++++++ src/ulib/concorrency/event.cpp | 64 +++++++++++++++++++ src/ulib/concorrency/event.h | 50 +++++++++++++++ .../internal/condition_variable_impl.cpp | 5 ++ .../internal/condition_variable_impl.h | 58 +++++++++++++++++ src/ulib/concorrency/internal/mutex_impl.cpp | 5 ++ src/ulib/concorrency/internal/mutex_impl.h | 31 +++++++++ src/ulib/concorrency/mutex.cpp | 36 +++++++++++ src/ulib/concorrency/mutex.h | 43 +++++++++++++ tests/CMakeLists.txt | 2 + tests/ulib/concorrency/event_test.cpp | 62 ++++++++++++++++++ tests/ulib/concorrency/mutex_test.cpp | 10 +++ 15 files changed, 475 insertions(+), 14 deletions(-) create mode 100644 src/ulib/concorrency/condition_variable.cpp create mode 100644 src/ulib/concorrency/condition_variable.h create mode 100644 src/ulib/concorrency/event.cpp create mode 100644 src/ulib/concorrency/event.h create mode 100644 src/ulib/concorrency/internal/condition_variable_impl.cpp create mode 100644 src/ulib/concorrency/internal/condition_variable_impl.h create mode 100644 src/ulib/concorrency/internal/mutex_impl.cpp create mode 100644 src/ulib/concorrency/internal/mutex_impl.h create mode 100644 src/ulib/concorrency/mutex.cpp create mode 100644 src/ulib/concorrency/mutex.h create mode 100644 tests/ulib/concorrency/event_test.cpp create mode 100644 tests/ulib/concorrency/mutex_test.cpp diff --git a/.gitea/workflows/linux-x64-gcc.yml b/.gitea/workflows/linux-x64-gcc.yml index f12f070..f664704 100644 --- a/.gitea/workflows/linux-x64-gcc.yml +++ b/.gitea/workflows/linux-x64-gcc.yml @@ -2,18 +2,18 @@ name: linux-x64-gcc on: push: paths: - - '.gitea/workflows/linux-x64-gcc.yml' - - 'src/**' - - 'tests/**' - - 'CMakeLists.txt' - - 'cmake/**' + - ".gitea/workflows/linux-x64-gcc.yml" + - "src/**" + - "tests/**" + - "CMakeLists.txt" + - "cmake/**" pull_request: paths: - - '.gitea/workflows/linux-x64-gcc.yml' - - 'src/**' - - 'tests/**' - - 'CMakeLists.txt' - - 'cmake/**' + - ".gitea/workflows/linux-x64-gcc.yml" + - "src/**" + - "tests/**" + - "CMakeLists.txt" + - "cmake/**" concurrency: group: linux-x64-gcc-${{ github.ref }} cancel-in-progress: true @@ -50,4 +50,5 @@ jobs: cmake --build build-shared -j `nproc` - name: test-shared run: | - cd build-shared && ctest --output-on-failure -j `nproc` \ No newline at end of file + cd build-shared && ctest --output-on-failure -j `nproc` + diff --git a/CMakeLists.txt b/CMakeLists.txt index 55ec159..4f63f0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,11 +8,21 @@ set(CMAKE_CXX_EXTENSIONS OFF) option(ULIB_BUILD_TESTS "Build tests" OFF) option(ULIB_SHARED_LIB "Build shared library" OFF) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) if (ULIB_SHARED_LIB) add_library(${PROJECT_NAME} SHARED "") - set(CMAKE_POSITION_INDEPENDENT_CODE ON) else() - add_library(${PROJECT_NAME} STATIC "") + add_library(${PROJECT_NAME} STATIC "" + src/ulib/concorrency/mutex.cpp + src/ulib/concorrency/mutex.h + src/ulib/concorrency/condition_variable.cpp + src/ulib/concorrency/condition_variable.h + src/ulib/concorrency/internal/mutex_impl.cpp + src/ulib/concorrency/internal/mutex_impl.h + src/ulib/concorrency/internal/condition_variable_impl.cpp + src/ulib/concorrency/internal/condition_variable_impl.h + src/ulib/concorrency/event.cpp + src/ulib/concorrency/event.h) endif() find_package(Threads REQUIRED) @@ -31,7 +41,6 @@ set(FMT_TEST OFF CACHE BOOL "Build tests" FORCE) set(FMT_USE_CPP11 OFF CACHE BOOL "Use C++11" FORCE) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3party/fmt) - target_sources(${PROJECT_NAME} PRIVATE src/ulib/empty.cpp src/ulib/log/logger.cpp diff --git a/src/ulib/concorrency/condition_variable.cpp b/src/ulib/concorrency/condition_variable.cpp new file mode 100644 index 0000000..dad33ef --- /dev/null +++ b/src/ulib/concorrency/condition_variable.cpp @@ -0,0 +1,38 @@ +// +// Created by Feng Zhang on 2023/12/12. +// + +#include "condition_variable.h" +#include "internal/condition_variable_impl.h" + +namespace ulib { + +ConditionVariable::ConditionVariable() : impl_(new detail::ConditionVariableImpl) {} + +ConditionVariable::~ConditionVariable() { delete impl_; } + +void +ConditionVariable::NotifyOne() +{ + impl_->NotifyOne(); +} + +void +ConditionVariable::NotifyAll() +{ + impl_->NotifyAll(); +} + +void +ConditionVariable::Wait(MutexGuard &guard) +{ + impl_->Wait(*guard.mutex_.impl_); +} + +bool +ConditionVariable::WaitForMilliseconds(MutexGuard &guard, uint32_t wait_time) +{ + return impl_->WaitForMilliseconds(*guard.mutex_.impl_, wait_time); +} + +} \ No newline at end of file diff --git a/src/ulib/concorrency/condition_variable.h b/src/ulib/concorrency/condition_variable.h new file mode 100644 index 0000000..8d70a03 --- /dev/null +++ b/src/ulib/concorrency/condition_variable.h @@ -0,0 +1,47 @@ +// +// Created by Feng Zhang on 2023/12/12. +// + +#ifndef ULIB_SRC_ULIB_CONCORRENCY_CONDITION_VARIABLE_H_ +#define ULIB_SRC_ULIB_CONCORRENCY_CONDITION_VARIABLE_H_ + +#include "mutex.h" +#include "ulib/base/types.h" + +namespace ulib { + +namespace detail { +class ConditionVariableImpl; +} + +class ConditionVariable { +public: + ConditionVariable(); + ~ConditionVariable(); + + void NotifyOne(); + void NotifyAll(); + + void Wait(MutexGuard &guard); + bool WaitForMilliseconds(MutexGuard &guard, uint32_t wait_time); + + template + void Wait(MutexGuard &guard, Predicate p) + { + while (!p()) { Wait(guard); } + } + + template + bool WaitForMilliseconds(MutexGuard &guard, uint32_t wait_time, Predicate p) + { + if (!p()) { WaitForMilliseconds(guard, wait_time); } + return p(); + } + +private: + detail::ConditionVariableImpl *impl_; +}; + +}// namespace ulib + +#endif//ULIB_SRC_ULIB_CONCORRENCY_CONDITION_VARIABLE_H_ diff --git a/src/ulib/concorrency/event.cpp b/src/ulib/concorrency/event.cpp new file mode 100644 index 0000000..3127573 --- /dev/null +++ b/src/ulib/concorrency/event.cpp @@ -0,0 +1,64 @@ +// +// Created by Feng Zhang on 2023/12/13. +// + +#include "event.h" + +ulib::Event::Event() : manual_reset_(false), event_status_(false) {} + +ulib::Event::Event(bool manual_reset, bool initially_signaled) + : manual_reset_(manual_reset), + event_status_(initially_signaled) +{} + +ulib::Event::~Event() {} + +void +ulib::Event::Set() +{ + MutexGuard guard(mutex_); + event_status_ = true; + cond_.NotifyAll(); +} + +void +ulib::Event::Reset() +{ + MutexGuard guard(mutex_); + event_status_ = false; +} + +bool +ulib::Event::Wait(int give_up_after_ms) +{ + MutexGuard guard(mutex_); + if (event_status_) { return true; } + + IsEventSetChecker checker(*this); + if (give_up_after_ms <= 0) { + cond_.Wait(guard, checker); + } else { + cond_.WaitForMilliseconds(guard, give_up_after_ms, checker); + } + + if (event_status_) { + if (manual_reset_) { event_status_ = false; } + return true; + } else { + return false; + } +} + +bool +ulib::Event::operator()() const +{ + return event_status_; +} + +ulib::Event::IsEventSetChecker::IsEventSetChecker(const ulib::Event &event) : event_(event) {} + +bool +ulib::Event::IsEventSetChecker::operator()() +{ + return event_.event_status_; +} diff --git a/src/ulib/concorrency/event.h b/src/ulib/concorrency/event.h new file mode 100644 index 0000000..a0fd184 --- /dev/null +++ b/src/ulib/concorrency/event.h @@ -0,0 +1,50 @@ +// +// Created by Feng Zhang on 2023/12/13. +// + +#ifndef ULIB_SRC_ULIB_CONCORRENCY_EVENT_H_ +#define ULIB_SRC_ULIB_CONCORRENCY_EVENT_H_ + +#include "mutex.h" +#include "condition_variable.h" + +namespace ulib { +class Event { +public: + Event(); + Event(bool manual_reset, bool initially_signaled); + ~Event(); + + void Set(); + void Reset(); + /** + * @brief + * @param give_up_after_ms -1, always wait, > 0 set timeout + * @return + */ + bool Wait(int give_up_after_ms = -1); + bool operator()() const; + +private: + class IsEventSetChecker { + public: + IsEventSetChecker(const Event &); + bool operator()(); + + private: + const Event &event_; + }; + + Event(const Event &); + Event &operator=(const Event &); + + const bool manual_reset_; + Mutex mutex_; + ConditionVariable cond_; + bool event_status_; + + friend class IsEventSetChecker; +}; +}// namespace ulib + +#endif//ULIB_SRC_ULIB_CONCORRENCY_EVENT_H_ diff --git a/src/ulib/concorrency/internal/condition_variable_impl.cpp b/src/ulib/concorrency/internal/condition_variable_impl.cpp new file mode 100644 index 0000000..3188365 --- /dev/null +++ b/src/ulib/concorrency/internal/condition_variable_impl.cpp @@ -0,0 +1,5 @@ +// +// Created by Feng Zhang on 2023/12/12. +// + +#include "condition_variable_impl.h" diff --git a/src/ulib/concorrency/internal/condition_variable_impl.h b/src/ulib/concorrency/internal/condition_variable_impl.h new file mode 100644 index 0000000..24547cc --- /dev/null +++ b/src/ulib/concorrency/internal/condition_variable_impl.h @@ -0,0 +1,58 @@ +// +// Created by Feng Zhang on 2023/12/12. +// + +#ifndef ULIB_SRC_ULIB_CONCORRENCY_INTERNAL_CONDITION_VARIABLE_IMPL_H_ +#define ULIB_SRC_ULIB_CONCORRENCY_INTERNAL_CONDITION_VARIABLE_IMPL_H_ + +#include "ulib/base/types.h" +#include "mutex_impl.h" +#include "ulib/concorrency/mutex.h" +#include +#include +#include + +namespace ulib { +namespace detail { + +class ConditionVariableImpl { +public: + ConditionVariableImpl() { pthread_cond_init(&cond_, NULL); } + + ~ConditionVariableImpl() { pthread_cond_destroy(&cond_); } + + void NotifyOne() { pthread_cond_signal(&cond_); } + + void NotifyAll() { pthread_cond_broadcast(&cond_); } + + void Wait(MutexImpl &mutex_impl) { pthread_cond_wait(&cond_, &mutex_impl.mutex_); } + + template + void Wait(MutexImpl &mutex_impl, Predicate p) + { + while (!p()) { Wait(mutex_impl); } + } + + bool WaitForMilliseconds(MutexImpl &mutex_impl, uint32_t wait_time) + { + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = wait_time * 1000000; + return ETIMEDOUT != pthread_cond_timedwait(&cond_, &mutex_impl.mutex_, &ts); + } + + template + bool WaitForMilliseconds(MutexImpl &mutex_impl, uint32_t wait_time, Predicate p) + { + if (!p()) { WaitForMilliseconds(mutex_impl, wait_time); } + return p(); + } + +private: + pthread_cond_t cond_; +}; + +} // namespace detail +}// namespace ulib + +#endif//ULIB_SRC_ULIB_CONCORRENCY_INTERNAL_CONDITION_VARIABLE_IMPL_H_ diff --git a/src/ulib/concorrency/internal/mutex_impl.cpp b/src/ulib/concorrency/internal/mutex_impl.cpp new file mode 100644 index 0000000..06ac2bb --- /dev/null +++ b/src/ulib/concorrency/internal/mutex_impl.cpp @@ -0,0 +1,5 @@ +// +// Created by Feng Zhang on 2023/12/12. +// + +#include "mutex_impl.h" diff --git a/src/ulib/concorrency/internal/mutex_impl.h b/src/ulib/concorrency/internal/mutex_impl.h new file mode 100644 index 0000000..29a83ed --- /dev/null +++ b/src/ulib/concorrency/internal/mutex_impl.h @@ -0,0 +1,31 @@ +// +// Created by Feng Zhang on 2023/12/12. +// + +#ifndef ULIB_SRC_ULIB_CONCORRENCY_INTERNAL_MUTEX_IMPL_H_ +#define ULIB_SRC_ULIB_CONCORRENCY_INTERNAL_MUTEX_IMPL_H_ + +#include + +namespace ulib { +namespace detail { +class MutexImpl { +public: + MutexImpl() { pthread_mutex_init(&mutex_, NULL); } + + ~MutexImpl() { pthread_mutex_destroy(&mutex_); } + + void Lock() { pthread_mutex_lock(&mutex_); } + + void Unlock() { pthread_mutex_unlock(&mutex_); } + + bool TryLock() { return pthread_mutex_trylock(&mutex_) == 0; } + +private: + friend class ConditionVariableImpl; + pthread_mutex_t mutex_; +}; +} // namespace detail +} // namespace ulib + +#endif//ULIB_SRC_ULIB_CONCORRENCY_INTERNAL_MUTEX_IMPL_H_ diff --git a/src/ulib/concorrency/mutex.cpp b/src/ulib/concorrency/mutex.cpp new file mode 100644 index 0000000..d3c0f3b --- /dev/null +++ b/src/ulib/concorrency/mutex.cpp @@ -0,0 +1,36 @@ +// +// Created by Feng Zhang on 2023/12/12. +// + +#include "mutex.h" +#include "internal/mutex_impl.h" + +namespace ulib { + +Mutex::Mutex() : impl_(new detail::MutexImpl) {} + +Mutex::~Mutex() { delete impl_; } + +void +Mutex::Lock() +{ + impl_->Lock(); +} + +void +Mutex::Unlock() +{ + impl_->Unlock(); +} + +bool +Mutex::TryLock() +{ + return impl_->TryLock(); +} + +MutexGuard::MutexGuard(Mutex &mutex) : mutex_(mutex) { mutex_.Lock(); } + +MutexGuard::~MutexGuard() { mutex_.Unlock(); } + +}// namespace ulib diff --git a/src/ulib/concorrency/mutex.h b/src/ulib/concorrency/mutex.h new file mode 100644 index 0000000..5e2cbb8 --- /dev/null +++ b/src/ulib/concorrency/mutex.h @@ -0,0 +1,43 @@ +// +// Created by Feng Zhang on 2023/12/12. +// + +#ifndef ULIB_SRC_ULIB_CONCORRENCY_MUTEX_H_ +#define ULIB_SRC_ULIB_CONCORRENCY_MUTEX_H_ + +namespace ulib { + +namespace detail { +class MutexImpl; +} +class Mutex { +public: + Mutex(); + ~Mutex(); + void Lock(); + void Unlock(); + bool TryLock(); + +private: + Mutex(const Mutex &); + Mutex &operator=(const Mutex &); + + friend class ConditionVariable; + detail::MutexImpl *impl_; +}; + +class MutexGuard { +public: + MutexGuard(Mutex& ); + ~MutexGuard(); +private: + MutexGuard(const MutexGuard&); + MutexGuard& operator=(const MutexGuard&); + + friend class ConditionVariable; + Mutex& mutex_; +}; + +} // namespace ulib + +#endif//ULIB_SRC_ULIB_CONCORRENCY_MUTEX_H_ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 98eb0b5..d18ac71 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,6 +4,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(ulib_test ulib/base/types_test.cpp ulib/log/log_test.cpp + ulib/concorrency/mutex_test.cpp + ulib/concorrency/event_test.cpp ) target_link_libraries(ulib_test PRIVATE ulib diff --git a/tests/ulib/concorrency/event_test.cpp b/tests/ulib/concorrency/event_test.cpp new file mode 100644 index 0000000..d73ca51 --- /dev/null +++ b/tests/ulib/concorrency/event_test.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include +#include + +class EventTest : public ::testing::Test { +public: + void SetUp() override + { + EXPECT_FALSE(event_()); + EXPECT_FALSE(event_.Wait(100)); + + consumer_count_ = 0; + } + + ulib::Event event_; + ulib::Mutex mutex_; + ulib::ConditionVariable cond_; + + int32_t consumer_count_; +}; + +void * +Consumer(void *args) +{ + EventTest *test = (EventTest *) args; + { + ulib::MutexGuard guard(test->mutex_); + ++test->consumer_count_; + test->cond_.NotifyAll(); + } + EXPECT_TRUE(test != NULL); + test->event_.Wait(); + { + ulib::MutexGuard guard(test->mutex_); + --test->consumer_count_; + test->cond_.NotifyAll(); + } + + return NULL; +} + +TEST_F(EventTest, multi_thread_wait_event) +{ + std::vector threads(10); + for (int i = 0; i < threads.size(); i++) { pthread_create(&threads[i], NULL, Consumer, this); } + + ulib::MutexGuard guard(mutex_); + while (consumer_count_ < threads.size()) { cond_.Wait(guard); } + EXPECT_EQ(consumer_count_, threads.size()); + + { + event_.Set(); + while (consumer_count_ > 0) { cond_.Wait(guard); } + } + + EXPECT_EQ(consumer_count_, 0); + for (int i = 0; i < threads.size(); i++) { pthread_join(threads[i], NULL); } +} diff --git a/tests/ulib/concorrency/mutex_test.cpp b/tests/ulib/concorrency/mutex_test.cpp new file mode 100644 index 0000000..d2e0f34 --- /dev/null +++ b/tests/ulib/concorrency/mutex_test.cpp @@ -0,0 +1,10 @@ +#include +#include + +TEST(MutexTest, MutexTest) +{ + ulib::Mutex mutex; + EXPECT_TRUE(mutex.TryLock()); + EXPECT_FALSE(mutex.TryLock()); + mutex.Unlock(); +}