From abc5c5a3743245ea49e26ed468112ea1215bc081 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Wed, 9 Sep 2015 19:13:27 -0400 Subject: [PATCH] new Thread::Config --- src/misc/thread.h | 185 ++++++++++++++++++++++++++++++++++++ testApp/misc/testThread.cpp | 113 +++++++++++++++++++--- 2 files changed, 287 insertions(+), 11 deletions(-) diff --git a/src/misc/thread.h b/src/misc/thread.h index 2c8e492..0f2dc94 100644 --- a/src/misc/thread.h +++ b/src/misc/thread.h @@ -11,6 +11,11 @@ #define THREAD_H #include +#include + +#if __cplusplus>=201103L +#include +#endif #ifdef epicsExportSharedSymbols #define threadepicsExportSharedSymbols @@ -47,12 +52,175 @@ typedef std::tr1::shared_ptr EpicsThreadPtr; typedef epicsThreadRunable Runnable; +//! Helper for those cases where a class should have more than one runnable +template +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 +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 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 p_owned_runner_t; +#else + typedef std::auto_ptr 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 + Config(C* inst, void(C::*meth)()) {this->x_setdefault();this->run(inst, meth);} +#if __cplusplus>=201103L + Config(const std::function& 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 + Config& run(C* inst, void(C::*meth)()) + { + this->p_owned_runner.reset(new detail::MethRunner(inst, meth)); + this->p_runner = this->p_owned_runner.get(); + return *this; + } +#if __cplusplus>=201103L + Config& run(const std::function& 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 + Config& operator<<(T x) { this->p_strm<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; }; diff --git a/testApp/misc/testThread.cpp b/testApp/misc/testThread.cpp index 189149f..aee569e 100644 --- a/testApp/misc/testThread.cpp +++ b/testApp/misc/testThread.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -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