new Thread::Config

This commit is contained in:
Michael Davidsaver
2015-09-09 19:13:27 -04:00
parent 54c94f181a
commit abc5c5a374
2 changed files with 287 additions and 11 deletions

View File

@@ -11,6 +11,11 @@
#define THREAD_H
#include <memory>
#include <sstream>
#if __cplusplus>=201103L
#include <functional>
#endif
#ifdef epicsExportSharedSymbols
#define threadepicsExportSharedSymbols
@@ -47,12 +52,175 @@ typedef std::tr1::shared_ptr<epicsThread> EpicsThreadPtr;
typedef epicsThreadRunable Runnable;
//! Helper for those cases where a class should have more than one runnable
template<typename C>
class epicsShareClass RunnableMethod : public Runnable, private NoDefaultMethods
{
typedef void (C::*meth_t)();
C *inst;
meth_t meth;
virtual void run()
{
(inst->*meth)();
}
public:
RunnableMethod(C* inst, void (C::*meth)())
:inst(inst), meth(meth)
{}
};
namespace detail {
struct FuncRunner : public epicsThreadRunable
{
typedef void (*fn_t)(void*);
fn_t fn;
void *arg;
FuncRunner(fn_t f, void *a) :fn(f), arg(a) {}
virtual ~FuncRunner(){}
virtual void run()
{
(*fn)(arg);
}
};
template<typename C>
struct MethRunner : public epicsThreadRunable
{
typedef void(C::*fn_t)();
fn_t fn;
C* inst;
MethRunner(C* i, fn_t f) :fn(f), inst(i) {}
virtual ~MethRunner() {}
virtual void run()
{
(inst->*fn)();
}
};
#if __cplusplus>=201103L
struct BindRunner : public epicsThreadRunable
{
typedef std::function<void()> fn_t;
fn_t fn;
BindRunner(const fn_t f) : fn(f) {}
virtual ~BindRunner() {}
virtual void run()
{
fn();
}
};
#endif
} // namespace detail
/**
* @brief C++ wrapper for epicsThread from EPICS base.
*
*/
class epicsShareClass Thread : public epicsThread, private NoDefaultMethods {
public:
/** @brief Holds all the configuration necessary to launch a @class Thread
*
* The defaults may be used except for the runnable, which must be given
* either in the constructor, or the @method run() method.
*
* @note Instances of @class Config may not be reused.
*
* Defaults:
* name: ""
* priority: epicsThreadPriorityLow (aka epics::pvData::lowestPriority)
* stack size: epicsThreadStackSmall
* auto start: true
* runner: nil (must be set explictly)
*
@code
stuct bar { void meth(); ... } X;
// with a static thread name
Thread foo(Thread::Config(&X, &bar::meth)
.name("example")
.prio(epicsThreadPriorityHigh));
// with a constructed thread name
Thread foo(Thread::Config(&X, &bar::meth)
.prio(epicsThreadPriorityHigh)
<<"example"<<1);
@endcode
*/
class epicsShareClass Config
{
unsigned int p_prio, p_stack;
std::ostringstream p_strm;
bool p_autostart;
Runnable *p_runner;
#if __cplusplus>=201103L
typedef std::unique_ptr<Runnable> p_owned_runner_t;
#else
typedef std::auto_ptr<Runnable> p_owned_runner_t;
#endif
p_owned_runner_t p_owned_runner;
friend class Thread;
Runnable& x_getrunner()
{
if(!this->p_runner)
throw std::logic_error("Thread::Config missing run()");
return *this->p_runner;
}
void x_setdefault()
{
this->p_prio = epicsThreadPriorityLow;
this->p_autostart = true;
this->p_runner = NULL;
(*this).stack(epicsThreadStackSmall);
}
public:
Config() {this->x_setdefault();}
Config(Runnable *r) {this->x_setdefault();this->run(r);}
Config(void(*fn)(void*), void *ptr) {this->x_setdefault();this->run(fn, ptr);}
template<typename C>
Config(C* inst, void(C::*meth)()) {this->x_setdefault();this->run(inst, meth);}
#if __cplusplus>=201103L
Config(const std::function<void()>& fn) {this->x_setdefault();this->run(fn);}
#endif
inline Config& name(const std::string& n)
{ this->p_strm.str(n); return *this; }
inline Config& prio(unsigned int p)
{ this->p_prio = p; return *this; }
inline Config& stack(epicsThreadStackSizeClass s)
{ this->p_stack = epicsThreadGetStackSize(s); return *this; }
inline Config& autostart(bool a)
{ this->p_autostart = a; return *this; }
//! Thread will execute Runnable::run()
Config& run(Runnable* r)
{ this->p_runner = r; return *this; }
//! Thread will execute (*fn)(ptr)
Config& run(void(*fn)(void*), void *ptr)
{
this->p_owned_runner.reset(new detail::FuncRunner(fn, ptr));
this->p_runner = this->p_owned_runner.get();
return *this;
}
//! Thread will execute (inst->*meth)()
template<typename C>
Config& run(C* inst, void(C::*meth)())
{
this->p_owned_runner.reset(new detail::MethRunner<C>(inst, meth));
this->p_runner = this->p_owned_runner.get();
return *this;
}
#if __cplusplus>=201103L
Config& run(const std::function<void()>& fn)
{
this->p_owned_runner.reset(new detail::BindRunner(fn));
this->p_runner = this->p_owned_runner.get();
return *this;
}
#endif
//! Append to thread name string. Argument must be understood by std::ostream::operator<<
template<typename T>
Config& operator<<(T x) { this->p_strm<<x; return *this; }
};
/**
*
@@ -108,6 +276,21 @@ public:
this->start();
}
//! @brief Create a new thread using the given @class Config
//! @throws std::logic_error for improper @class Config (ie. missing runner)
Thread(Config& c)
:epicsThread(c.x_getrunner(), c.p_strm.str().c_str(),
c.p_stack, c.p_prio)
{
#if __cplusplus>=201103L
p_owned = std::move(c.p_owned_runner);
#else
p_owned = c.p_owned_runner;
#endif
if(c.p_autostart)
this->start();
}
/**
* Destructor
*/
@@ -115,6 +298,8 @@ public:
{
this->exitWait();
}
Config::p_owned_runner_t p_owned;
};

View File

@@ -15,6 +15,7 @@
#include <cstddef>
#include <string>
#include <cstdio>
#include <cstring>
#include <list>
#include <epicsUnitTest.h>
@@ -42,11 +43,11 @@ public:
Event begin, end;
Action(): actuallyRan(false) {}
virtual void run() {
printf("Action waiting\n");
testDiag("Action waiting");
begin.signal();
bool waited=end.wait();
actuallyRan=true;
printf("Action %s\n", waited?"true":"false");
testDiag("Action %s", waited?"true":"false");
}
};
@@ -56,13 +57,13 @@ static void testThreadRun() {
{
ThreadPtr tr(new Thread(actionName,lowPriority,ax.get()));
bool w=ax->begin.wait();
printf( "main %s\n", w?"true":"false");
printf( "Action is %s\n", ax->actuallyRan?"true":"false");
testDiag( "main %s", w?"true":"false");
testDiag( "Action is %s", ax->actuallyRan?"true":"false");
ax->end.signal();
}
testOk1(ax->actuallyRan==true);
printf( "Action is %s\n", ax->actuallyRan?"true":"false");
printf("testThreadRun PASSED\n");
testDiag( "Action is %s", ax->actuallyRan?"true":"false");
testDiag("testThreadRun PASSED");
}
class Basic;
@@ -84,7 +85,7 @@ public:
executor->execute(getPtrSelf());
bool result = wait.wait();
testOk1(result==true);
if(result==false) printf("basic::run wait returned false\n");
if(result==false) testDiag("basic::run wait returned false");
}
virtual void command()
{
@@ -105,7 +106,96 @@ static void testBasic() {
ExecutorPtr executor(new Executor(string("basic"),middlePriority));
BasicPtr basic( new Basic(executor));
basic->run();
printf("testBasic PASSED\n");
testDiag("testBasic PASSED");
}
namespace {
struct fninfo {
int cnt;
epicsEvent evnt;
};
static void threadFN(void *raw)
{
fninfo *arg = (fninfo*)raw;
arg->evnt.signal();
arg->cnt++;
}
struct classMeth {
int cnt;
epicsEvent evnt;
classMeth() :cnt(0) {}
void inc() {
const char *tname = epicsThreadGetNameSelf();
testOk(strcmp(tname, "test2")==0, "thread name '%s' == 'test2' ", tname);
evnt.signal();
cnt++;
}
};
}
static void testBinders()
{
testDiag("Testing thread bindables");
testDiag("C style function");
{
fninfo info;
info.cnt = 0;
Thread foo(Thread::Config(&threadFN, (void*)&info)
.name("test1")
.prio(epicsThreadPriorityMedium)
.autostart(true)
);
info.evnt.wait();
foo.exitWait();
testOk(info.cnt==1, "cnt (%d) == 1", info.cnt);
}
testDiag("class method");
{
classMeth inst;
Thread foo(Thread::Config(&inst, &classMeth::inc)
.prio(epicsThreadPriorityMedium)
.autostart(false)
<<"test"<<2
);
epicsThreadSleep(0.1);
testOk(inst.cnt==0, "inst.cnt (%d) == 0", inst.cnt);
foo.start();
inst.evnt.wait();
foo.exitWait();
testOk(inst.cnt==1, "inst.cnt (%d) == 1", inst.cnt);
}
testDiag("C++11 style lambda");
#if __cplusplus>=201103L
{
int cnt = 0;
epicsEvent evnt;
auto fn = [&cnt,&evnt]() mutable {evnt.signal(); cnt++;};
Thread foo(Thread::Config(fn)
.name("test3")
.prio(epicsThreadPriorityMedium)
.autostart(true)
);
evnt.wait();
foo.exitWait();
testOk(cnt==1, "cnt (%d) == 1", cnt);
}
#else
testSkip(1, "Not built as C++11");
#endif
}
class MyFunc : public TimeFunctionRequester {
@@ -137,17 +227,18 @@ static void testThreadContext() {
TimeFunctionPtr timeFunction(new TimeFunction(myFunc));
double perCall = timeFunction->timeCall();
perCall *= 1e6;
printf("time per call %f microseconds\n",perCall);
printf("testThreadContext PASSED\n");
testDiag("time per call %f microseconds",perCall);
testDiag("testThreadContext PASSED");
}
#endif
MAIN(testThread)
{
testPlan(2);
testPlan(7);
testDiag("Tests thread");
testThreadRun();
testBasic();
testBinders();
#ifdef TESTTHREADCONTEXT
testThreadContext();
#endif