Library: identified further use-case variants to cover
...while reworking the application code, it became clear that actually there are two further quite distinct variants of usage. And while these could be implemented with some trickery based on the Thread-wrapper defined thus far, it seems prudent better to establish a safely confined explicit setup for these cases: - a fire-and-forget-thread, which manages its own memory autonomously - a thread with explicit lifecycle, with detectable not-running state
This commit is contained in:
parent
b00d435ab6
commit
88b91d204c
7 changed files with 1529 additions and 797 deletions
|
|
@ -352,6 +352,8 @@ namespace lib {
|
|||
* in a situation where the thread totally manages itself and the
|
||||
* thread object is maintained in a unique_ptr. You must ensure that
|
||||
* the thread function only uses storage within its own scope.
|
||||
* @deprecated can't sleep well while this function is exposed;
|
||||
* need a prime solution to address this relevant use case ////////////////////////////////////////OOO allow for a thread with explicit lifecycle
|
||||
*/
|
||||
void detach() { ThreadLifecycle::handle_end_of_thread(); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ namespace stage {
|
|||
{
|
||||
try
|
||||
{
|
||||
Thread {"GUI-Main", bind (&runGUI, terminationHandle)};
|
||||
Thread {"GUI-Main", bind (&runGUI, terminationHandle)}; ///////////////////////////////////////////OOO this shows we need a self-contained and detached thread!
|
||||
return true; // if we reach this line...
|
||||
}
|
||||
catch(...)
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@ namespace control {
|
|||
}
|
||||
// leave the Session thread...
|
||||
// send notification of subsystem shutdown
|
||||
thread_.detach();
|
||||
thread_.detach();/////////////////////////////////////////////////////////////OOO while this case is exceptional, it still mandates better framework support
|
||||
notifyEnd (&errorMsg); // invokes ~DispatcherLoop()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,16 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
PLANNED "Launch self-contained detached Thread" ThreadWrapperAutonomous_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
PLANNED "Detect Thread lifecycle state changes" ThreadWrapperLifecycle_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
TEST "Detect running in specific Thread" ThreadWrapperSelfRecognitionTest_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
|
|
|||
145
tests/library/thread-wrapper-autonomous-test.cpp
Normal file
145
tests/library/thread-wrapper-autonomous-test.cpp
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
ThreadWrapperAutonomous(Test) - launching a self-contained and completely detached thread
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2023, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
* *****************************************************/
|
||||
|
||||
/** @file thread-wrapper-autonomous-test.cpp
|
||||
** unit test \ref ThreadWrapperAutonomous_test
|
||||
*/
|
||||
|
||||
|
||||
#include "lib/test/run.hpp"
|
||||
#include "lib/thread.hpp"
|
||||
#include "lib/iter-explorer.hpp"
|
||||
#include "lib/scoped-collection.hpp"
|
||||
#include "lib/test/microbenchmark.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
||||
using test::Test;
|
||||
using lib::explore;
|
||||
using std::atomic_uint;
|
||||
using std::this_thread::yield;
|
||||
using std::this_thread::sleep_for;
|
||||
using std::chrono::microseconds;
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace test{
|
||||
|
||||
namespace { // test parameters
|
||||
|
||||
const uint NUM_THREADS = 200;
|
||||
const uint REPETITIONS = 10;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************//**
|
||||
* @test a variation of the Thread wrapper to launch a detached thread,
|
||||
* with automatic memory management for the _thread-object._.
|
||||
* @see thread.hpp
|
||||
* @see ThreadWrapper_test
|
||||
*/
|
||||
class ThreadWrapperAutonomous_test : public Test
|
||||
{
|
||||
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
demonstrateSimpleUsage();
|
||||
verifyMemoryManagement();
|
||||
}
|
||||
|
||||
|
||||
/** @test demonstrate simply launching a λ-function into background */
|
||||
void
|
||||
demonstrateSimpleUsage()
|
||||
{
|
||||
atomic_uint i{0};
|
||||
Thread thread("counter", [&]{ ++i; }); // bind a λ and launch thread
|
||||
while (thread) yield(); // ensure thread has finished and detached
|
||||
|
||||
CHECK (i == 1); // verify the effect has taken place
|
||||
UNIMPLEMENTED ("actually launch detached");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @test verify the detached thread autonomously manages its memory.
|
||||
*/
|
||||
void
|
||||
verifyMemoryManagement()
|
||||
{
|
||||
struct TestThread
|
||||
: Thread
|
||||
{
|
||||
using Thread::Thread;
|
||||
|
||||
uint local{0};
|
||||
|
||||
void
|
||||
doIt (uint a, uint b) ///< the actual operation running in a separate thread
|
||||
{
|
||||
uint sum = a + b;
|
||||
sleep_for (microseconds{sum}); // Note: explicit random delay before local store
|
||||
local = sum;
|
||||
}
|
||||
};
|
||||
|
||||
// prepare Storage for these objects (not created yet)
|
||||
lib::ScopedCollection<TestThread> threads{NUM_THREADS};
|
||||
|
||||
size_t checkSum = 0;
|
||||
size_t globalSum = 0;
|
||||
auto launchThreads = [&]
|
||||
{
|
||||
for (uint i=1; i<=NUM_THREADS; ++i)
|
||||
{
|
||||
uint x = rand() % 1000;
|
||||
globalSum += (i + x);
|
||||
threads.emplace (&TestThread::doIt, i, x);
|
||||
} // Note: bind to member function, copying arguments
|
||||
|
||||
while (explore(threads).has_any())
|
||||
yield(); // wait for all threads to have detached
|
||||
|
||||
for (auto& t : threads)
|
||||
{
|
||||
CHECK (0 < t.local);
|
||||
checkSum += t.local;
|
||||
}
|
||||
};
|
||||
|
||||
double runTime = benchmarkTime (launchThreads, REPETITIONS);
|
||||
|
||||
CHECK (checkSum == globalSum); // sum of precomputed random numbers matches sum from threads
|
||||
CHECK (runTime < NUM_THREADS * 1000/2); // random sleep time should be > 500ms on average
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** Register this test class... */
|
||||
LAUNCHER (ThreadWrapperAutonomous_test, "function common");
|
||||
|
||||
|
||||
|
||||
}} // namespace lib::test
|
||||
146
tests/library/thread-wrapper-lifecycle-test.cpp
Normal file
146
tests/library/thread-wrapper-lifecycle-test.cpp
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
ThreadWrapperLifecycle(Test) - verify lifecycle aspects of the thread wrapper
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2023, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
* *****************************************************/
|
||||
|
||||
/** @file thread-wrapper-lifecycle-test.cpp
|
||||
** unit test \ref ThreadWrapperLifecycle_test
|
||||
*/
|
||||
|
||||
|
||||
#include "lib/test/run.hpp"
|
||||
#include "lib/thread.hpp"
|
||||
#include "lib/iter-explorer.hpp"
|
||||
#include "lib/scoped-collection.hpp"
|
||||
#include "lib/test/microbenchmark.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
||||
using test::Test;
|
||||
using lib::explore;
|
||||
using std::atomic_uint;
|
||||
using std::this_thread::yield;
|
||||
using std::this_thread::sleep_for;
|
||||
using std::chrono::microseconds;
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace test{
|
||||
|
||||
namespace { // test parameters
|
||||
|
||||
const uint NUM_THREADS = 200;
|
||||
const uint REPETITIONS = 10;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************//**
|
||||
* @test verify lifecycle behaviour of threads managed by thread-wrapper.
|
||||
* @see thread.hpp
|
||||
* @see ThreadWrapperBackground_test
|
||||
* @see ThreadWrapperJoin_test
|
||||
*/
|
||||
class ThreadWrapperLifecycle_test : public Test
|
||||
{
|
||||
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
defaultWrapperLifecycle();
|
||||
verifyExplicitLifecycleState();
|
||||
}
|
||||
|
||||
|
||||
/** @test demonstrate terms of lifecycle for the default case */
|
||||
void
|
||||
defaultWrapperLifecycle()
|
||||
{
|
||||
atomic_uint i{0};
|
||||
Thread thread("counter", [&]{ ++i; }); // bind a λ and launch thread
|
||||
while (thread) yield(); // ensure thread has finished and detached
|
||||
|
||||
CHECK (i == 1); // verify the effect has taken place
|
||||
UNIMPLEMENTED ("demonstrate state change");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @test verify a special setup to start a thread explicitly and to track
|
||||
* the thread's lifecycle state.
|
||||
*/
|
||||
void
|
||||
verifyExplicitLifecycleState()
|
||||
{
|
||||
struct TestThread
|
||||
: Thread
|
||||
{
|
||||
using Thread::Thread;
|
||||
|
||||
uint local{0};
|
||||
|
||||
void
|
||||
doIt (uint a, uint b) ///< the actual operation running in a separate thread
|
||||
{
|
||||
uint sum = a + b;
|
||||
sleep_for (microseconds{sum}); // Note: explicit random delay before local store
|
||||
local = sum;
|
||||
}
|
||||
};
|
||||
|
||||
// prepare Storage for these objects (not created yet)
|
||||
lib::ScopedCollection<TestThread> threads{NUM_THREADS};
|
||||
|
||||
size_t checkSum = 0;
|
||||
size_t globalSum = 0;
|
||||
auto launchThreads = [&]
|
||||
{
|
||||
for (uint i=1; i<=NUM_THREADS; ++i)
|
||||
{
|
||||
uint x = rand() % 1000;
|
||||
globalSum += (i + x);
|
||||
threads.emplace (&TestThread::doIt, i, x);
|
||||
} // Note: bind to member function, copying arguments
|
||||
|
||||
while (explore(threads).has_any())
|
||||
yield(); // wait for all threads to have detached
|
||||
|
||||
for (auto& t : threads)
|
||||
{
|
||||
CHECK (0 < t.local);
|
||||
checkSum += t.local;
|
||||
}
|
||||
};
|
||||
|
||||
double runTime = benchmarkTime (launchThreads, REPETITIONS);
|
||||
|
||||
CHECK (checkSum == globalSum); // sum of precomputed random numbers matches sum from threads
|
||||
CHECK (runTime < NUM_THREADS * 1000/2); // random sleep time should be > 500ms on average
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** Register this test class... */
|
||||
LAUNCHER (ThreadWrapperLifecycle_test, "function common");
|
||||
|
||||
|
||||
|
||||
}} // namespace lib::test
|
||||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue