replace GThreads by the Lumiera thread wrapper

This commit is contained in:
Fischlurch 2010-02-11 03:06:42 +01:00
parent 5386fe6fbc
commit dfd70c6069
6 changed files with 453 additions and 481 deletions

View file

@ -60,8 +60,7 @@ namespace backend {
*
*/
class Thread
: lib::BoolCheckable< Thread
, boost::noncopyable> //////TODO: do we want Thread instances to be copyable?
: boost::noncopyable //////TODO: do we want Thread instances to be copyable?
{
protected:
@ -136,15 +135,6 @@ namespace backend {
ThreadStartContext (thread_, operation, purpose, logging_flag);
}
/** automatic conversion: Thread instance can stand-in for a handle
* @throws lumiera::error::State when thread isn't running */
operator LumieraThread() const
{
if (!isValid())
throw lumiera::error::State("thread not executing (anymore)");
return thread_;
}
bool
isValid() const
@ -178,12 +168,12 @@ namespace backend {
* to join on the termination of this thread.
*/
class ThreadJoinable
: public Thread
: public lib::BoolCheckable<ThreadJoinable
,Thread> // baseclass
{
public:
ThreadJoinable (Literal purpose, Operation const& operation,
NoBugFlag logging_flag = &NOBUG_FLAG(thread))
: Thread()
{
ThreadStartContext (thread_, operation, purpose, logging_flag,
LUMIERA_THREAD_JOINABLE);

View file

@ -26,80 +26,75 @@
#include "lib/sync-classlock.hpp"
//#include <iostream>
//using std::cout;
using test::Test;
namespace lib {
namespace test {
namespace test {
namespace { // private test classes and data...
namespace { // private test classes and data...
const uint NUM_INSTANCES = 20; ///< number of probe instances to create
/**
* Several instances of this probe class will be created.
* Each of them acquires the shared lock; but anyway, just
* by defining this class, the embedded Monitor got created.
*/
struct Probe
{
ClassLock<Probe> shared_lock_;
Probe() {}
~Probe() {}
};
} // (End) test classes and data....
const uint NUM_INSTANCES = 20; ///< number of probe instances to create
/**************************************************************************
* @test check proper handling of class (not instance)-based Monitor locks.
* Because no instance is available in this case, a hidden storage for the
* Monitor object needs to be provided in a way safe for use even in the
* static startup/shutdown phase. This test validates the associated
* refcounting and object creation works as expected. It does \em not
* validate the locking functionality as such.
*
* @see sync.hpp
/**
* Several instances of this probe class will be created.
* Each of them acquires the shared lock; but anyway, just
* by defining this class, the embedded Monitor got created.
*/
class SyncClasslock_test : public Test
struct Probe
{
ClassLock<Probe> shared_lock_;
virtual void
run (Arg)
{
{
Probe objs[NUM_INSTANCES];
ASSERT (1 == objs[0].shared_lock_.use_count());
}
ClassLock<Probe> get_class_lock;
ASSERT ( 1 == get_class_lock.use_count()); // embedded PerClassMonitor<Probe> got created exactly once
} // and stays alive until static dtors are called....
Probe() {}
~Probe() {}
};
/** Register this test class... */
LAUNCHER (SyncClasslock_test, "unit common");
} // namespace test
} // namespace lib
} // (End) test classes and data....
/**************************************************************************
* @test check proper handling of class (not instance)-based Monitor locks.
* Because no instance is available in this case, a hidden storage for the
* Monitor object needs to be provided in a way safe for use even in the
* static startup/shutdown phase. This test validates the associated
* refcounting and object creation works as expected. It does \em not
* validate the locking functionality as such.
*
* @see sync.hpp
*/
class SyncClasslock_test : public Test
{
virtual void
run (Arg)
{
{
Probe objs[NUM_INSTANCES];
ASSERT (1 == objs[0].shared_lock_.use_count());
}
ClassLock<Probe> get_class_lock;
ASSERT ( 1 == get_class_lock.use_count()); // embedded PerClassMonitor<Probe> got created exactly once
} // and stays alive until static dtors are called....
};
/** Register this test class... */
LAUNCHER (SyncClasslock_test, "unit common");
}} // namespace lib::test

View file

@ -25,186 +25,178 @@
#include "lib/error.hpp"
#include "lib/sync.hpp"
#include <glibmm.h>
#include "backend/thread-wrapper.hpp"
#include <iostream>
#include <tr1/functional>
using std::tr1::bind;
using std::cout;
using test::Test;
namespace lib {
namespace test {
namespace test{
namespace { // private test classes and data...
const uint NUM_COUNTERS = 20; ///< number of independent counters to increment in parallel
const uint NUM_THREADS = 10; ///< number of threads trying to increment these counters
const uint MAX_PAUSE = 10000; ///< maximum delay implemented as empty counting loop
const uint MAX_SUM = 1000; ///< trigger when to finish incrementing
const uint MAX_INC = 10; ///< maximum increment on each step
class Victim
: public Sync<RecursiveLock_NoWait>
{
volatile long cnt_[NUM_COUNTERS];
volatile uint step_; ///< @note stored as instance variable
void
pause ()
{
Lock guard (this); // note recursive lock
for ( uint i=0, lim=(rand() % MAX_PAUSE); i<lim; ++i)
;
}
void
incrementAll ()
{
for (uint i=0; i<NUM_COUNTERS; ++i)
{
pause();
cnt_[i] += step_;
}
}
public:
Victim ()
{
for (uint i=0; i<NUM_COUNTERS; ++i)
cnt_[i] = 0;
}
void
inc (uint newStep)
{
Lock guard (this);
step_ = newStep;
incrementAll();
}
bool
belowLimit ()
{
Lock guard (this);
return cnt_[0] < MAX_SUM;
}
bool
checkAllEqual ()
{
for (uint i=1; i<NUM_COUNTERS; ++i)
if (cnt_[i-1] != cnt_[i])
return false;
return true;
}
void
report ()
{
for (uint i=0; i<NUM_COUNTERS; ++i)
cout << "Counter-#" << i << " = " << cnt_[i] << "\n";
}
}
ourVictim;
/**
* A Thread trying to increment all victim counters in sync...
*/
class HavocThread
{
Glib::Thread * thread_;
void
doIt ()
{
while (ourVictim.belowLimit())
ourVictim.inc (rand() % MAX_INC);
}
public:
HavocThread ()
: thread_(0)
{ }
~HavocThread ()
{
if (thread_)
thread_->join();
}
void
start ()
{
thread_ = Glib::Thread::create(sigc::mem_fun(*this, &HavocThread::doIt), true);
ASSERT (thread_);
}
};
} // (End) test classes and data....
namespace { // private test classes and data...
const uint NUM_COUNTERS = 20; ///< number of independent counters to increment in parallel
const uint NUM_THREADS = 10; ///< number of threads trying to increment these counters
const uint MAX_PAUSE = 10000; ///< maximum delay implemented as empty counting loop
const uint MAX_SUM = 1000; ///< trigger when to finish incrementing
const uint MAX_INC = 10; ///< maximum increment on each step
/**********************************************************************
* @test create multiple threads, all concurrently trying to increment
* a number of counters with random steps and random pauses. Without
* locking, the likely result will be differing counters.
* But because the class Victim uses an object level monitor to
* guard the mutations, the state should remain consistent.
*
* @see SyncWaiting_test condition based wait/notify
* @see SyncClasslock_test locking a type, not an instance
* @see sync.hpp
*/
class SyncLocking_test : public Test
class Victim
: public Sync<RecursiveLock_NoWait>
{
volatile long cnt_[NUM_COUNTERS];
volatile uint step_; ///< @note stored as instance variable
virtual void
run (Arg)
void
pause ()
{
if (!Glib::thread_supported())
Glib::thread_init();
Lock guard (this); // note recursive lock
REQUIRE (ourVictim.checkAllEqual());
{
HavocThread threads[NUM_THREADS];
for (uint i=0; i<NUM_THREADS; ++i)
threads[i].start();
}
// all finished and joined here...
if (!ourVictim.checkAllEqual())
for ( uint i=0, lim=(rand() % MAX_PAUSE); i<lim; ++i)
;
}
void
incrementAll ()
{
for (uint i=0; i<NUM_COUNTERS; ++i)
{
cout << "Thread locking is broken; internal state got messed up\n"
"NOTE: all counters should be equal and >=" << MAX_SUM << "\n";
ourVictim.report();
pause();
cnt_[i] += step_;
}
}
public:
Victim ()
{
for (uint i=0; i<NUM_COUNTERS; ++i)
cnt_[i] = 0;
}
void
inc (uint newStep)
{
Lock guard (this);
step_ = newStep;
incrementAll();
}
bool
belowLimit ()
{
Lock guard (this);
return cnt_[0] < MAX_SUM;
}
bool
checkAllEqual ()
{
for (uint i=1; i<NUM_COUNTERS; ++i)
if (cnt_[i-1] != cnt_[i])
return false;
return true;
}
void
report ()
{
for (uint i=0; i<NUM_COUNTERS; ++i)
cout << "Counter-#" << i << " = " << cnt_[i] << "\n";
}
}
ourVictim;
/**
* A Thread trying to increment all victim counters in sync...
*/
class HavocThread
{
backend::ThreadJoinable thread_;
void
doIt ()
{
while (ourVictim.belowLimit())
ourVictim.inc (rand() % MAX_INC);
}
public:
HavocThread ()
: thread_("HavocThread"
, bind (&HavocThread::doIt, this)
)
{
ASSERT (thread_);
}
~HavocThread ()
{
if (thread_)
thread_.join();
}
};
} // (End) test classes and data....
/**********************************************************************
* @test create multiple threads, all concurrently trying to increment
* a number of counters with random steps and random pauses. Without
* locking, the likely result will be differing counters.
* But because the class Victim uses an object level monitor to
* guard the mutations, the state should remain consistent.
*
* @see SyncWaiting_test condition based wait/notify
* @see SyncClasslock_test locking a type, not an instance
* @see sync.hpp
*/
class SyncLocking_test : public Test
{
/** Register this test class... */
LAUNCHER (SyncLocking_test, "unit common");
} // namespace test
} // namespace lib
virtual void
run (Arg)
{
REQUIRE (ourVictim.checkAllEqual());
{
HavocThread threads[NUM_THREADS] SIDEEFFECT;
}
// all finished and joined here...
if (!ourVictim.checkAllEqual())
{
cout << "Thread locking is broken; internal state got messed up\n"
"NOTE: all counters should be equal and >=" << MAX_SUM << "\n";
ourVictim.report();
}
}
};
/** Register this test class... */
LAUNCHER (SyncLocking_test, "unit common");
}} // namespace lib::test

View file

@ -33,108 +33,106 @@ using test::Test;
namespace lib {
namespace test {
namespace test{
namespace { // private test classes and data...
namespace { // private test classes and data...
const uint WAIT_mSec = 200; ///< milliseconds to wait before timeout
const uint WAIT_mSec = 200; ///< milliseconds to wait before timeout
} // (End) test classes and data....
/********************************************************************************
* @test timeout feature on condition wait as provided by pthread and accessible
* via the object monitor based locking/waiting mechanism. Without creating
* multiple threads, we engage into a blocking wait, which aborts due to
* setting a timeout. (Note it is discouraged to use the timed wait feature;
* when possible, you should prefer relying on the Lumiera scheduler)
*
* @see SyncWaiting_test
* @see sync::Timeout
* @see sync.hpp
*/
class SyncTimedwait_test
: public Test,
Sync<RecursiveLock_Waitable>
{
} // (End) test classes and data....
/********************************************************************************
* @test timeout feature on condition wait as provided by pthread and accessible
* via the object monitor based locking/waiting mechanism. Without creating
* multiple threads, we engage into a blocking wait, which aborts due to
* setting a timeout. (Note it is discouraged to use the timed wait feature;
* when possible, you should prefer relying on the Lumiera scheduler)
*
* @see SyncWaiting_test
* @see sync::Timeout
* @see sync.hpp
*/
class SyncTimedwait_test
: public Test,
Sync<RecursiveLock_Waitable>
{
friend class Lock; // allows inheriting privately from Sync
virtual void
run (Arg)
{
checkTimeoutStruct();
Lock block(this, &SyncTimedwait_test::neverHappens);
cout << "back from LaLaLand, alive and thriving!\n";
ASSERT (block.isTimedWait());
}
bool
neverHappens() ///< the "condition test" used for waiting....
{
Lock currentLock(this); // get the Lock recursively
if (!currentLock.isTimedWait()) // right from within the condition test:
currentLock.setTimeout(WAIT_mSec); // switch waiting mode to timed wait and set timeout
return false;
}
void
checkTimeoutStruct()
{
sync::Timeout tout;
ASSERT (!tout);
ASSERT (0 == tout.tv_sec);
ASSERT (0 == tout.tv_nsec);
tout.setOffset (0);
ASSERT (!tout);
ASSERT (0 == tout.tv_sec);
ASSERT (0 == tout.tv_nsec);
timespec ref;
clock_gettime(CLOCK_REALTIME, &ref);
tout.setOffset (1);
ASSERT (tout);
ASSERT (0 < tout.tv_sec);
ASSERT (ref.tv_sec <= tout.tv_sec);
ASSERT (ref.tv_nsec <= 1000000 + tout.tv_nsec || ref.tv_nsec > 1000000000-100000);
clock_gettime(CLOCK_REALTIME, &ref);
tout.setOffset (1000);
ASSERT (tout);
if (ref.tv_nsec!=0) // should have gotten an overflow to the seconds part
{
ASSERT (ref.tv_sec <= 2 + tout.tv_sec );
ASSERT ((ref.tv_nsec + 1000000 * 999) % 1000000000
<= tout.tv_nsec);
}
}
};
friend class Lock; // allows inheriting privately from Sync
/** Register this test class... */
LAUNCHER (SyncTimedwait_test, "unit common");
} // namespace test
} // namespace lib
virtual void
run (Arg)
{
checkTimeoutStruct();
Lock block(this, &SyncTimedwait_test::neverHappens);
cout << "back from LaLaLand, alive and thriving!\n";
ASSERT (block.isTimedWait());
}
bool
neverHappens() ///< the "condition test" used for waiting....
{
Lock currentLock(this); // get the Lock recursively
if (!currentLock.isTimedWait()) // right from within the condition test:
currentLock.setTimeout(WAIT_mSec); // switch waiting mode to timed wait and set timeout
return false;
}
void
checkTimeoutStruct()
{
sync::Timeout tout;
ASSERT (!tout);
ASSERT (0 == tout.tv_sec);
ASSERT (0 == tout.tv_nsec);
tout.setOffset (0);
ASSERT (!tout);
ASSERT (0 == tout.tv_sec);
ASSERT (0 == tout.tv_nsec);
timespec ref;
clock_gettime(CLOCK_REALTIME, &ref);
tout.setOffset (1);
ASSERT (tout);
ASSERT (0 < tout.tv_sec);
ASSERT (ref.tv_sec <= tout.tv_sec);
ASSERT (ref.tv_nsec <= 1000000 + tout.tv_nsec || ref.tv_nsec > 1000000000-100000);
clock_gettime(CLOCK_REALTIME, &ref);
tout.setOffset (1000);
ASSERT (tout);
if (ref.tv_nsec!=0) // should have gotten an overflow to the seconds part
{
ASSERT (ref.tv_sec <= 2 + tout.tv_sec );
ASSERT ((ref.tv_nsec + 1000000 * 999) % 1000000000
<= tout.tv_nsec);
}
}
};
/** Register this test class... */
LAUNCHER (SyncTimedwait_test, "unit common");
}} // namespace lib::test

View file

@ -24,165 +24,162 @@
#include "lib/test/run.hpp"
#include "lib/error.hpp"
#include "backend/thread-wrapper.hpp"
#include "lib/sync.hpp"
#include <glibmm.h> //////////////TODO temp solution, replace by backend!
#include <tr1/functional>
using std::tr1::bind;
using test::Test;
namespace lib {
namespace test {
namespace test{
namespace { // private test classes and data...
/** Interface defining the basic interaction pattern for this test */
class Token
{
public:
/** blocking concurrent operation */
virtual void getIt() =0;
/** start the notification chain */
virtual void provide (uint val) =0;
/** harvesting the result...*/
uint result () { return sum_; }
protected:
volatile uint sum_, input_;
virtual ~Token() {}
Token() : sum_(0), input_(0) {}
};
/** demonstrates how to wait on a simple boolean flag */
class SyncOnBool
: public Token,
public Sync<NonrecursiveLock_Waitable>
{
protected:
volatile bool got_new_data_;
public:
SyncOnBool() : got_new_data_ (false) {}
void getIt()
{
Lock(this).wait (got_new_data_);
sum_ += input_;
}
void provide (uint val)
{
Lock sync(this);
input_ = val;
got_new_data_ = true;
sync.notifyAll();
}
};
/** this variant demonstrates how to wait on an condition
* defined in terms of a (bool) member function
*/
class SyncOnMemberPredicate
: public SyncOnBool
{
bool checkTheFlag() { return this->got_new_data_; }
public:
void getIt()
{
Lock guard(this, &SyncOnMemberPredicate::checkTheFlag);
sum_ += input_;
}
};
} // (End) test classes and data....
namespace { // private test classes and data...
/****************************************************************************
* @test concurrent waiting and notification, implemented via object monitor.
* This test covers the second part of the monitor pattern, which builds upon
* the locking part an additionally uses an embedded condition. We provide
* several pre-configured ways of specifying the condition to wait upon.
* - check a boolean flag
* - evaluate a member function as predicate
*
* @see SyncLocking_test
* @see sync.hpp
*/
class SyncWaiting_test : public Test
/** Interface defining the basic interaction pattern for this test */
class Token
{
public:
/** blocking concurrent operation */
virtual void getIt() =0;
virtual void
run (Arg)
/** start the notification chain */
virtual void provide (uint val) =0;
/** harvesting the result...*/
uint result () { return sum_; }
protected:
volatile uint sum_, input_;
virtual ~Token() {}
Token() : sum_(0), input_(0) {}
};
/** demonstrates how to wait on a simple boolean flag */
class SyncOnBool
: public Token,
public Sync<NonrecursiveLock_Waitable>
{
protected:
volatile bool got_new_data_;
public:
SyncOnBool() : got_new_data_ (false) {}
void getIt()
{
if (!Glib::thread_supported())
Glib::thread_init();
SyncOnBool use_sync_var;
waitPingPong (use_sync_var);
SyncOnMemberPredicate use_member_pred;
waitPingPong (use_member_pred);
Lock(this).wait (got_new_data_);
sum_ += input_;
}
/**
* Helper actually performing the test:
* creates two threads and let them block and wait until a start value is given.
* When awakened, each thread should add the start value to a common sum field.
* @param tok object containing the monitor and condition to be tested.
*/
void
waitPingPong (Token& tok)
void provide (uint val)
{
Glib::Thread *ping, *pong;
ping = Glib::Thread::create(sigc::mem_fun(tok, &Token::getIt), true);
pong = Glib::Thread::create(sigc::mem_fun(tok, &Token::getIt), true);
ASSERT (ping);
ASSERT (pong);
ASSERT (0 == tok.result());
usleep (100000); // if the threads don't block correctly, they've missed their chance by now...
// kick off the notification cascade...
uint val = (rand() % 1000);
tok.provide (val);
// wait for the two Threads to finish their handshake
pong->join();
ping->join();
ASSERT (2*val == tok.result());
Lock sync(this);
input_ = val;
got_new_data_ = true;
sync.notifyAll();
}
};
/** this variant demonstrates how to wait on an condition
* defined in terms of a (bool) member function
*/
class SyncOnMemberPredicate
: public SyncOnBool
{
bool checkTheFlag() { return this->got_new_data_; }
public:
void getIt()
{
Lock await(this, &SyncOnMemberPredicate::checkTheFlag);
sum_ += input_;
}
};
} // (End) test classes and data....
/****************************************************************************
* @test concurrent waiting and notification, implemented via object monitor.
* This test covers the second part of the monitor pattern, which builds upon
* the locking part, additionally using an embedded condition. We provide
* several pre-configured ways of specifying the condition to wait upon.
* - check a boolean flag
* - evaluate a member function as predicate
*
* @see SyncLocking_test
* @see sync.hpp
*/
class SyncWaiting_test : public Test
{
/** Register this test class... */
LAUNCHER (SyncWaiting_test, "unit common");
} // namespace test
} // namespace lib
virtual void
run (Arg)
{
SyncOnBool use_sync_var;
waitPingPong (use_sync_var);
SyncOnMemberPredicate use_member_pred;
waitPingPong (use_member_pred);
}
/**
* Helper actually performing the test:
* creates two threads and let them block and wait until a start value is given.
* When awakened, each thread should add the start value to a common sum field.
* @param tok object containing the monitor and condition to be tested.
*/
void
waitPingPong (Token& tok)
{
typedef backend::ThreadJoinable Thread;
Thread ping ("SyncWaiting ping", bind (&Token::getIt, &tok));
Thread pong ("SyncWaiting pong", bind (&Token::getIt, &tok));
ASSERT (ping);
ASSERT (pong);
ASSERT (0 == tok.result());
usleep (100000); // if the threads don't block correctly, they've missed their chance by now...
// kick off the notification cascade...
uint val = (rand() % 1000);
tok.provide (val);
// wait for the two Threads to finish their handshake
pong.join();
ping.join();
ASSERT (2*val == tok.result());
}
};
/** Register this test class... */
LAUNCHER (SyncWaiting_test, "unit common");
}} // namespace lib::test

View file

@ -42,7 +42,7 @@ namespace test {
using lumiera::error::LUMIERA_ERROR_LOGIC;
namespace {
const uint DESTRUCTION_CODE = 23;
const int DESTRUCTION_CODE = 23;
LUMIERA_ERROR_DEFINE(SPECIAL, "grandiose exception");
}