From 44cc89cd80a5d95a182f719ebe76d8862faf4871 Mon Sep 17 00:00:00 2001 From: Christian Kern Date: Wed, 4 Nov 2015 12:23:49 +0100 Subject: [PATCH] Added Spinlock implementation and basic tests. --- base_c/include/embb/base/c/mutex.h | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ base_c/src/mutex.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ base_c/test/main.cc | 3 ++- base_c/test/mutex_test.cc | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ base_c/test/mutex_test.h | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ base_cpp/include/embb/base/mutex.h | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ base_cpp/src/mutex.cc | 3 --- base_cpp/test/main.cc | 3 +++ base_cpp/test/mutex_test.cc | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- base_cpp/test/mutex_test.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 473 insertions(+), 11 deletions(-) diff --git a/base_c/include/embb/base/c/mutex.h b/base_c/include/embb/base/c/mutex.h index ffd2c7f..f3c8450 100644 --- a/base_c/include/embb/base/c/mutex.h +++ b/base_c/include/embb/base/c/mutex.h @@ -46,12 +46,25 @@ extern "C" { #include #include +#include #ifdef DOXYGEN /** * Opaque type representing a mutex. */ typedef opaque_type embb_mutex_t; + +/** + * Opaque type representing a spinlock. + */ +typedef opaque_type embb_spinlock_t; +#else +/** + * Spinlock type, treat as opaque. + */ +typedef struct { + embb_atomic_int atomic_spin_variable_; +} embb_spinlock_t; #endif /* DOXYGEN */ /** @@ -147,6 +160,82 @@ void embb_mutex_destroy( /**< [IN/OUT] Pointer to mutex */ ); +/** + * Initializes a spinlock + * + * \post \c spinlock is initialized + * \return EMBB_SUCCESS if spinlock could be initialized \n + * EMBB_ERROR otherwise + * \memory (Potentially) allocates dynamic memory + * \notthreadsafe + * \see embb_spinlock_destroy() + */ +int embb_spin_init( + embb_spinlock_t* spinlock + /**< [OUT] Pointer to spinlock */ + ); + +/** + * Spins until the spinlock can be locked and locks it. + * + * \pre \c spinlock is initialized \n + * \post If successful, \c spinlock is locked. + * \return EMBB_SUCCESS if spinlock could be locked. + * \threadsafe + * \see embb_spinlock_try_lock(), embb_mutex_unlock() + */ +int embb_spin_lock( + embb_spinlock_t* spinlock + /**< [IN/OUT] Pointer to spinlock */ + ); + +/** + * Tries to lock the spinlock and returns if not successful. + * + * \pre \c spinlock is initialized + * \post If successful, \c spinlock is locked + * + * \return EMBB_SUCCESS if spinlock could be locked \n + * EMBB_BUSY if spinlock could not be locked \n + * \threadsafe + * \see embb_spin_lock(), embb_spin_unlock() + */ +int embb_spin_try_lock( + embb_spinlock_t* spinlock, + /**< [IN/OUT] Pointer to spinlock */ + unsigned int max_number_spins + /**< [IN] Number of attempts the locking operation is repeated if + * unsuccessful */ + ); + +/** + * Unlocks a locked spinlock. + * + * \pre \c spinlock has been locked by the current thread. + * \post If successful, \c spinlock is unlocked. + * \return EMBB_SUCCESS if the operation was successful \n + * EMBB_ERROR otherwise + * \threadsafe + * \see embb_spin_lock(), embb_spin_try_lock() + */ +int embb_spin_unlock( + embb_spinlock_t* spinlock + /**< [IN/OUT] Pointer to spinlock */ + ); + +/** + * Destroys a spinlock and frees its resources. + * + * \pre \c spinlock has been initialized + * \post \c spinlock is uninitialized + * \notthreadsafe + * \see embb_spin_init() + */ +void embb_spin_destroy( + embb_spinlock_t* spinlock + /**< [IN/OUT] Pointer to spinlock */ + ); + #ifdef __cplusplus } /* Close extern "C" { */ #endif diff --git a/base_c/src/mutex.c b/base_c/src/mutex.c index cd32696..a749ac9 100644 --- a/base_c/src/mutex.c +++ b/base_c/src/mutex.c @@ -115,3 +115,57 @@ void embb_mutex_destroy(embb_mutex_t* mutex) { } #endif /* EMBB_PLATFORM_THREADING_POSIXTHREADS */ + +int embb_spin_init(embb_spinlock_t* spinlock) { + // for now, just assign the internal struct value... in the future, + // we will have an atomic init function. + spinlock->atomic_spin_variable_.internal_variable = 0; +} + +int embb_spin_lock(embb_spinlock_t* spinlock) { + int expected = 0; + + // try to swap the + while (0 == embb_atomic_compare_and_swap_int( + &spinlock->atomic_spin_variable_, &expected, 1)) { + // mtapi has a debug variable, counting spins... think about that... + // embb_atomic_fetch_and_add_int(&embb_mtapi_spinlock_spins, 1); + + // reset expected, as CAS might change it... + expected = 0; + } + return EMBB_SUCCESS; +} + +int embb_spin_try_lock(embb_spinlock_t* spinlock, + unsigned int max_number_spins) { + int expected = 0; + unsigned int spin_count = max_number_spins; + while (0 == embb_atomic_compare_and_swap_int( + &spinlock->atomic_spin_variable_, + &expected, 1)) { + // mtapi has a debug variable, counting spins... think about that... + // embb_atomic_fetch_and_add_int(&embb_mtapi_spinlock_spins, 1); + + spin_count--; + if (0 == spin_count) { + return EMBB_BUSY; + } + expected = 0; + } + + return EMBB_SUCCESS; +} + +int embb_spin_unlock(embb_spinlock_t* spinlock) { + int expected = 1; + return embb_atomic_compare_and_swap_int(&spinlock->atomic_spin_variable_, + &expected, 0) ? + EMBB_SUCCESS : EMBB_ERROR; +} + +void embb_spin_destroy(embb_spinlock_t* spinlock) { + // for now, doing nothing here... in future, will call the respective + // destroy function for atomics... + return EMBB_SUCCESS; +} diff --git a/base_c/test/main.cc b/base_c/test/main.cc index 54ce031..4e26f30 100644 --- a/base_c/test/main.cc +++ b/base_c/test/main.cc @@ -46,6 +46,7 @@ using embb::base::test::DurationTest; using embb::base::test::TimeTest; using embb::base::test::CounterTest; using embb::base::test::MutexTest; +using embb::base::test::SpinLockTest; using embb::base::test::ThreadIndexTest; using embb::base::test::CoreSetTest; using embb::base::test::ConditionVarTest; @@ -63,11 +64,11 @@ PT_MAIN("Base C") { PT_RUN(TimeTest); PT_RUN(CounterTest); PT_RUN(MutexTest); + PT_RUN(SpinLockTest); PT_RUN(ThreadIndexTest); PT_RUN(CoreSetTest); PT_RUN(ConditionVarTest); PT_RUN(ThreadTest); PT_RUN(ThreadSpecificStorageTest); - PT_EXPECT(embb_get_bytes_allocated() == 0); } diff --git a/base_c/test/mutex_test.cc b/base_c/test/mutex_test.cc index ab2be92..e173385 100644 --- a/base_c/test/mutex_test.cc +++ b/base_c/test/mutex_test.cc @@ -76,6 +76,77 @@ void MutexTest::TestRecursiveMutex() { embb_mutex_destroy(&mutex); } +SpinLockTest::SpinLockTest() : counter_(0), +number_threads_(partest::TestSuite::GetDefaultNumThreads()), + number_iterations_(partest::TestSuite::GetDefaultNumIterations()), + counter_iterations_(10000) { + CreateUnit("Protected counter using Lock") + .Pre(&SpinLockTest::PreSpinLockInc, this) + .Add(&SpinLockTest::TestSpinLockIncUseLock, this, + number_threads_, + number_iterations_) + .Post(&SpinLockTest::PostSpinLockInc, this); + + CreateUnit("Protected counter using TryLock") + .Pre(&SpinLockTest::PreSpinLockInc, this) + .Add(&SpinLockTest::TestSpinLockIncUseTryLock, this, + number_threads_, + number_iterations_) + .Post(&SpinLockTest::PostSpinLockInc, this); + + CreateUnit("Test spinning (too many spins), single thread") + .Add(&SpinLockTest::TestSpinLockTooManySpins, this, + // one thread + 1, + // one iteration + 1); +} + +void SpinLockTest::TestSpinLockTooManySpins() { + embb_spin_init(&spinlock_); + embb_spin_lock(&spinlock_); + + int return_code = embb_spin_try_lock(&spinlock_, 100); + PT_ASSERT(return_code == EMBB_BUSY); + + embb_spin_unlock(&spinlock_); + + return_code = embb_spin_try_lock(&spinlock_, 100); + PT_ASSERT(return_code == EMBB_SUCCESS); + + embb_spin_unlock(&spinlock_); + + embb_spin_destroy(&spinlock_); +} + +void SpinLockTest::PreSpinLockInc() { + embb_spin_init(&spinlock_); +} + +void SpinLockTest::TestSpinLockIncUseLock() { + for (unsigned int i = 0; i != counter_iterations_; ++i){ + embb_spin_lock(&spinlock_); + counter_++; + embb_spin_unlock(&spinlock_); + } +} + +void SpinLockTest::TestSpinLockIncUseTryLock() { + for (unsigned int i = 0; i != counter_iterations_; ++i){ + while (embb_spin_try_lock(&spinlock_, 100) != EMBB_SUCCESS) {} + counter_++; + embb_spin_unlock(&spinlock_); + } +} + +void SpinLockTest::PostSpinLockInc() { + embb_spin_destroy(&spinlock_); + PT_EXPECT_EQ(counter_, number_iterations_ * + number_threads_* + counter_iterations_); + counter_ = 0; +} + } // namespace test } // namespace base } // namespace embb diff --git a/base_c/test/mutex_test.h b/base_c/test/mutex_test.h index 012df6f..0359d47 100644 --- a/base_c/test/mutex_test.h +++ b/base_c/test/mutex_test.h @@ -85,6 +85,63 @@ class MutexTest : public partest::TestCase { size_t number_iterations_; }; + +class SpinLockTest : public partest::TestCase { + public: + SpinLockTest(); + private: + /** + * Check that the try lock fails, when lock is already set. + */ + void TestSpinLockTooManySpins(); + + /** + * Prepares TestMutexIncCpp. + */ + void PreSpinLockInc(); + + /** + * Tests mutex locking and unlocking to protect shared counter. + */ + void TestSpinLockIncUseLock(); + + /** + * Tests mutex locking and unlocking to protect shared counter using trylock. + */ + void TestSpinLockIncUseTryLock(); + + /** + * Checks and tears down TestMutexIncCpp. + */ + void PostSpinLockInc(); + + /** + * Shared counter to check effectiveness of mutex. + */ + size_t counter_; + + /** + * Number of threads used to run tests. + */ + size_t number_threads_; + + /** + * Number of times the test method is called by each thread. + */ + size_t number_iterations_; + + /** + * Number of internal iterations, for incrementing the counter. + */ + size_t counter_iterations_; + + /** + * The used spinlock + */ + embb_spinlock_t spinlock_; +}; + + } // namespace test } // namespace base } // namespace embb diff --git a/base_cpp/include/embb/base/mutex.h b/base_cpp/include/embb/base/mutex.h index 1d63027..a2b88ee 100644 --- a/base_cpp/include/embb/base/mutex.h +++ b/base_cpp/include/embb/base/mutex.h @@ -29,10 +29,10 @@ #include #include +#include namespace embb { namespace base { - /** * \defgroup CPP_BASE_MUTEX Mutex and Lock * @@ -47,7 +47,6 @@ namespace base { class ConditionVariable; namespace internal { - /** * Provides main functionality for mutexes. */ @@ -111,10 +110,111 @@ class MutexBase { */ friend class embb::base::ConditionVariable; }; - } // namespace internal /** + * \defgroup CPP_BASE_SPINLOCK Spinlock + * + * Spinlock for thread synchronization. + * + * \ingroup CPP_BASE + */ + +/** + * Spinlock + * + * \ingroup CPP_BASE_SPINLOCK + */ +class Spinlock { + public: + /** + * Creates a spinlock which is in unlocked state. + * + * \notthreadsafe + */ + Spinlock() { + embb_spin_init(&spinlock_); + } + + /** + * Destructs a spinlock. + * + * \notthreadsafe + */ + ~Spinlock() { + embb_spin_destroy(&spinlock_); + } + + /** + * Waits until the spinlock can be locked and locks it. + * + * \pre The spinlock is not locked by the current thread. + * \post The spinlock is locked + * \threadsafe + * \see TryLock(), Unlock() + */ + void Lock() { + int status = embb_spin_lock(&spinlock_); + + // Currently, embb_spin_lock will always return EMBB_SUCCESS. However, + // This might change. + if (status != EMBB_SUCCESS) { + EMBB_THROW(ErrorException, "Error in embb_spin_lock"); + } + } + + /** + * Tries to lock the spinlock for \c number_spins times and returns. + * + * \pre The spinlock is not locked by the current thread. + * \post If successful, the spinlock is locked. + * \return \c true if the spinlock could be locked, otherwise \c false. + * \threadsafe + * \see Lock(), Unlock() + */ + bool TryLock(unsigned int number_spins = 1) { + int status = embb_spin_try_lock(&spinlock_, number_spins); + + if (status == EMBB_BUSY){ + return false; + } + else if (status != EMBB_SUCCESS) { + EMBB_THROW(ErrorException, "Error in embb_spin_try_lock"); + } + + return true; + } + + /** + * Unlocks the spinlock. + * + * \pre The spinlock is locked by the current thread + * \post The spinlock is unlocked + * \threadsafe + * \see Lock(), TryLock() + */ + void Unlock() { + int status = embb_spin_unlock(&spinlock_); + + if (status != EMBB_SUCCESS) { + EMBB_THROW(ErrorException, "Error in embb_spin_unlock"); + } + } + + private: + /** + * Disables copy construction and assignment. + */ + Spinlock(const Spinlock&); + Spinlock& operator=(const Spinlock&); + + /** + * Internal spinlock from base_c + */ + embb_spinlock_t spinlock_; +}; + +/** * Non-recursive, exclusive mutex. * * Mutexes of this type cannot be locked recursively, that is, multiple times @@ -182,7 +282,6 @@ class Mutex : public internal::MutexBase { friend class ConditionVariable; }; - /** * Recursive, exclusive mutex. * @@ -246,7 +345,6 @@ class RecursiveMutex : public internal::MutexBase { RecursiveMutex& operator=(const RecursiveMutex&); }; - /** * Scoped lock (according to the RAII principle) using a mutex. * @@ -482,7 +580,6 @@ class UniqueLock { */ friend class embb::base::ConditionVariable; }; - } // namespace base } // namespace embb diff --git a/base_cpp/src/mutex.cc b/base_cpp/src/mutex.cc index 61693ba..f18f6d9 100644 --- a/base_cpp/src/mutex.cc +++ b/base_cpp/src/mutex.cc @@ -62,6 +62,3 @@ RecursiveMutex::RecursiveMutex() : MutexBase(EMBB_MUTEX_RECURSIVE) { } // namespace base } // namespace embb - - - diff --git a/base_cpp/test/main.cc b/base_cpp/test/main.cc index 5d7626d..2e84368 100644 --- a/base_cpp/test/main.cc +++ b/base_cpp/test/main.cc @@ -41,6 +41,7 @@ using embb::base::test::CoreSetTest; using embb::base::test::DurationTest; using embb::base::test::ConditionVarTest; using embb::base::test::MutexTest; +using embb::base::test::SpinLockTest; using embb::base::test::ThreadSpecificStorageTest; using embb::base::test::AtomicTest; using embb::base::test::MemoryAllocationTest; @@ -50,10 +51,12 @@ PT_MAIN("Base C++") { unsigned int max_threads = static_cast(2 * partest::TestSuite::GetDefaultNumThreads()); embb_thread_set_max_count(max_threads); + PT_RUN(CoreSetTest); PT_RUN(DurationTest); PT_RUN(ConditionVarTest); PT_RUN(MutexTest); + PT_RUN(SpinLockTest); PT_RUN(ThreadSpecificStorageTest); PT_RUN(AtomicTest); PT_RUN(MemoryAllocationTest); diff --git a/base_cpp/test/mutex_test.cc b/base_cpp/test/mutex_test.cc index 48cc0a9..571a4a2 100644 --- a/base_cpp/test/mutex_test.cc +++ b/base_cpp/test/mutex_test.cc @@ -32,7 +32,6 @@ namespace embb { namespace base { namespace test { - MutexTest::MutexTest() : mutex_(), counter_(0), number_threads_(partest::TestSuite::GetDefaultNumThreads()), number_iterations_(partest::TestSuite::GetDefaultNumIterations()) { @@ -209,6 +208,57 @@ void MutexTest::TestUniqueLock() { } } +SpinLockTest::SpinLockTest() : spinlock_(), counter_(0), +number_threads_(partest::TestSuite::GetDefaultNumThreads()), + number_iterations_(partest::TestSuite::GetDefaultNumIterations()), + counter_iterations_(10000) { + CreateUnit("Spinlock protected counter (using Lock)") + .Add(&SpinLockTest::TestSpinlockCountLock, this, number_threads_, + number_iterations_) + .Post(&SpinLockTest::PostSpinlockCount, this); + + CreateUnit("Spinlock protected counter (using Trylock)") + .Add(&SpinLockTest::TestSpinlockCountLockTryLock, this, number_threads_, + number_iterations_) + .Post(&SpinLockTest::PostSpinlockCount, this); + + CreateUnit("Test spinning (too many spins), single thread") + .Add(&SpinLockTest::TestSpinLockTooManySpins, this, 1, 1); +} + +void SpinLockTest::TestSpinlockCountLock() { + for (unsigned int i = 0; i != counter_iterations_; ++i){ + spinlock_.Lock(); + counter_++; + spinlock_.Unlock(); + } +} + +void SpinLockTest::TestSpinlockCountLockTryLock() { + for (unsigned int i = 0; i != counter_iterations_; ++i){ + while (!spinlock_.TryLock()) {} + counter_++; + spinlock_.Unlock(); + } +} + +void SpinLockTest::PostSpinlockCount() { + PT_EXPECT_EQ(counter_, + number_iterations_ * + number_threads_* + counter_iterations_); + counter_ = 0; +} + +void SpinLockTest::TestSpinLockTooManySpins() { + Spinlock lock; + lock.Lock(); + bool success = lock.TryLock(100); + PT_ASSERT(!success); + lock.Unlock(); + success = lock.TryLock(100); + PT_ASSERT(success); +} } // namespace test } // namespace base } // namespace embb diff --git a/base_cpp/test/mutex_test.h b/base_cpp/test/mutex_test.h index 80579a8..d301058 100644 --- a/base_cpp/test/mutex_test.h +++ b/base_cpp/test/mutex_test.h @@ -89,6 +89,49 @@ class MutexTest : public partest::TestCase { size_t number_iterations_; }; +class SpinLockTest : public partest::TestCase { +public: + SpinLockTest(); +private: + + /** + * Uses Spinlock to realize multi-threaded counting. + */ + void TestSpinlockCountLock(); + void TestSpinlockCountLockTryLock(); + void PostSpinlockCount(); + + /** + * Test that TryLock returns false, if lock is already locked. + */ + void TestSpinLockTooManySpins(); + + /** + * Spinlock for tests + */ + Spinlock spinlock_; + + /** + * Shared counter to check effectiveness of mutex. + */ + size_t counter_; + + /** + * Number of threads used to run tests. + */ + size_t number_threads_; + + /** + * Number of times the test method is called by each thread. + */ + size_t number_iterations_; + + /** + * Number of internal iterations, for incrementing the counter. + */ + size_t counter_iterations_; +}; + } // namespace test } // namespace base } // namespace embb -- libgit2 0.26.0