diff --git a/src/thread/MT_Semaphore.cpp b/src/thread/MT_Semaphore.cpp index 14b5414..f3c96dd 100644 --- a/src/thread/MT_Semaphore.cpp +++ b/src/thread/MT_Semaphore.cpp @@ -15,18 +15,17 @@ namespace RLib // *********************************************************************************************** void MT_Semaphore::mt_notify() { - int count = 0; - sem_getvalue(&mt_sem_, &count); // impossible failed since MT_Semaphore's constructore - if (count > 0) // >0 to avoid count overflow; timeout is deadline - return; - sem_post(&mt_sem_); // impossible failed since MT_Semaphore's constructor + // - can't sem_getvalue() as NOT mt-safe + // - mt_notified_ is to avoid sem counter overflow; & not rouse main-thread repeatedly + if (!mt_notified_.test_and_set()) // memory_order_seq_cst to ensure other thread(s) see the flag + sem_post(&mt_sem_); // impossible failed since MT_Semaphore's constructor } // *********************************************************************************************** void MT_Semaphore::timedwait(const size_t aSec, const size_t aRestNsec) { timespec ts{0, 0}; - clock_gettime(CLOCK_REALTIME, &ts); // impossible failed since MT_Semaphore's constructor + clock_gettime(CLOCK_REALTIME, &ts); const auto ns = ts.tv_nsec + aRestNsec; // usr's duty for reasonable aRestNsec; here's duty for no crash ts.tv_sec += (aSec + ns / 1000'000'000); @@ -35,10 +34,11 @@ void MT_Semaphore::timedwait(const size_t aSec, const size_t aRestNsec) for (;;) { const auto ret = sem_timedwait(&mt_sem_, &ts); - if (ret == 0) // notified - return; - else if (errno == ETIMEDOUT) + if (errno == ETIMEDOUT || ret == 0) // timeout or notified -> wakeup to handle sth + { + mt_notified_.clear(); // memory_order_seq_cst to ensure other thread(s) see the flag return; + } // impossible since MT_Semaphore's constructor // else if (errno == EINVAL) // avoid dead loop diff --git a/src/thread/MT_Semaphore.hpp b/src/thread/MT_Semaphore.hpp index a9b0934..338c355 100644 --- a/src/thread/MT_Semaphore.hpp +++ b/src/thread/MT_Semaphore.hpp @@ -20,6 +20,7 @@ // *********************************************************************************************** #pragma once +#include #include using namespace std; @@ -30,20 +31,30 @@ namespace RLib class MT_Semaphore { public: - MT_Semaphore() { sem_init(&mt_sem_, 0, 0); } // 2nd para: intra-process; 3rd: init value - ~MT_Semaphore() { sem_destroy(&mt_sem_); } - MT_Semaphore(const MT_Semaphore&) = delete; // then compiler will not auto-gen mv(), =() etc + MT_Semaphore() noexcept { sem_init(&mt_sem_, 0, 0); } // 2nd para: intra-process; 3rd: init value + ~MT_Semaphore() noexcept { sem_destroy(&mt_sem_); } + MT_Semaphore(const MT_Semaphore&) = delete; // then compiler NOT auto-gen mv(), =() etc - void mt_notify(); + void mt_notify() noexcept; - // - no mt_ prefix since main thread use only + // - no mt_ prefix since main-thread use ONLY // - if more threads call it, not guarantee to wakeup all threads but only 1 - // . so mt_timedwait() is more complex, will impl if real req appears - void timedwait(const size_t aSec = 0, const size_t aRestNsec = 100'000'000); + void timedwait(const size_t aSec = 0, const size_t aRestNsec = 100'000'000) noexcept; - // ------------------------------------------------------------------------------------------- private: sem_t mt_sem_; + atomic_flag mt_notified_ = ATOMIC_FLAG_INIT; // = false + + // ------------------------------------------------------------------------------------------- +#ifdef RLIB_UT +public: + void reset() noexcept // not mt safe + { + sem_destroy(&mt_sem_); + sem_init(&mt_sem_, 0, 0); + mt_notified_.clear(); + } +#endif }; } // namespace @@ -53,4 +64,12 @@ class MT_Semaphore // 2023-09-20 CSZ 1)create // 2023-09-21 CSZ - timer based on ThreadBack // 2023-10-26 CSZ - timer based on sem_timedwait() +// 2024-07-04 CSZ - sem_getvalue() is not MT safe, replaced // *********************************************************************************************** +// Q&A: +// - why not check sem_*() failure? +// . ENOMEM: sem exceeds max# - impossible normally/mostly +// . EINVAL: invalid para - impossible +// . EPERM : no right - impossible normally/mostly +// . ENOSYS: not support sem - impossible normally/mostly +// . EBUSY : re-init sem - impossible diff --git a/ut/thread/MT_SemaphoreTest.cpp b/ut/thread/MT_SemaphoreTest.cpp index 965931d..7c5de16 100644 --- a/ut/thread/MT_SemaphoreTest.cpp +++ b/ut/thread/MT_SemaphoreTest.cpp @@ -13,11 +13,14 @@ #include #include "MsgSelf.hpp" -#include "MT_PingMainTH.hpp" -#include "MtInQueue.hpp" #include "ThreadBackViaMsgSelf.hpp" #include "UniLog.hpp" +#define RLIB_UT +#include "MT_PingMainTH.hpp" +#include "MtInQueue.hpp" +#undef RLIB_UT + using namespace std::chrono; using namespace testing; @@ -27,7 +30,9 @@ struct MT_SemaphoreTest : public Test, public UniLog { MT_SemaphoreTest() : UniLog(UnitTest::GetInstance()->current_test_info()->name()) - {} + { + g_sem.reset(); // clear prev test's counter + } void SetUp() override { mt_getQ().mt_clear(); // avoid other case interfere