new Thread::Config
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user