Commit 44cc89cd by Christian Kern

Added Spinlock implementation and basic tests.

parent be3ead1e
......@@ -46,12 +46,25 @@ extern "C" {
#include <embb/base/c/internal/platform.h>
#include <embb/base/c/errors.h>
#include <embb/base/c/atomic.h>
#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
......
......@@ -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;
}
......@@ -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);
}
......@@ -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
......@@ -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
......
......@@ -29,10 +29,10 @@
#include <embb/base/internal/platform.h>
#include <embb/base/exceptions.h>
#include <embb/base/c/mutex.h>
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
......
......@@ -62,6 +62,3 @@ RecursiveMutex::RecursiveMutex() : MutexBase(EMBB_MUTEX_RECURSIVE) {
} // namespace base
} // namespace embb
......@@ -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<unsigned int>(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);
......
......@@ -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
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment