I want to pass in an arbitrary number of functions together with their arguments to a function called startThread
so that it can run them concurrently.
My code is below but obviously, it has syntactic errors:
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <exception>
int test1( int i, double d )
{
// do something...
using namespace std::chrono_literals;
std::this_thread::sleep_for( 3000ms );
return 0;
}
int test2( char c )
{
// do something...
using namespace std::chrono_literals;
std::this_thread::sleep_for( 2000ms );
return 0;
}
template< class Fn, class... Args > // how should I write the template and startThread params to
int startThread( Fn&&... fns, Args&&... args ) // get arbitrary functions as threads and their own arguments?
{
std::vector< std::thread > threads;
threads.push_back( std::thread( test1, 2, 65.2 ) ); // how to automate the task of starting the
threads.push_back( std::thread( test2, 'A' ) ); // threads instead of writing them one by one?
std::cout << "synchronizing all threads...\n";
for ( auto& th : threads ) th.join();
return 0;
}
int main( )
{
int successIndicator { };
try
{
successIndicator = startThread( test1( 2, 65.2 ), test2( 'A' ) ); // what should be passed to startThread?
} // How to pass the arguments?
catch ( const std::exception& e )
{
successIndicator = -1;
std::cerr << e.what( ) << '\n';
}
return successIndicator;
}
Thanks in advance.
CodePudding user response:
This is how I would do it, using a recursive template function. And std::async instead of std::thread
#include <future>
#include <chrono>
#include <thread>
#include <iostream>
void test1(int /*i*/, double /*d*/)
{
std::cout << "Test1 start\n";
std::this_thread::sleep_for(std::chrono::milliseconds(300));
std::cout << "Test1 done\n";
}
void test2(bool)
{
std::cout << "Test2 start\n";
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << "Test2 done\n";
}
//-----------------------------------------------------------------------------
// Recursive template function that will start all passed functions
// and then waits for them to be finished one by one.
// this will still be limited by the slowest function
// so no need to store them in a collection or something
template<typename Fn, typename... Fns>
void run_parallel(Fn fn, Fns&&... fns)
{
// I prefer using std::async instead of std::thread
// it has a better abstraction and futures
// allow passing of exceptions back to the calling thread.
auto future = std::async(std::launch::async, fn);
// are there any more functions to start then do so
if constexpr (sizeof...(fns) > 0)
{
run_parallel(std::forward<Fns>(fns)...);
}
future.get();
}
//-----------------------------------------------------------------------------
int main()
{
std::cout << "main start\n";
// start all functions by creating lambdas for them
run_parallel(
[]() { test1(1, 1.10); },
[]() { test2(true); }
);
std::cout << "main done\n";
}
CodePudding user response:
You can pack these functions into a tuple
, and then pack the parameters corresponding to each function into a tuple
, and then pass them into startThread()
together, then expand the function through std::apply
, and then expand the corresponding parameters through std::apply
and pass them into the function. Something like this:
template<class FunTuple, class... ArgsTuple>
int startThread(FunTuple fun_tuple, ArgsTuple... args_tuple) {
std::vector<std::thread> threads;
std::apply([&](auto... fun) {
(threads.emplace_back(
[&] { std::apply([&](auto... args) { fun(args...); }, args_tuple); }
), ...);
}, fun_tuple);
std::cout << "synchronizing all threads...\n";
for (auto& th : threads ) th.join();
return 0;
}
Then you can invoke startThread()
like this:
startThread(std::tuple(test1, test2), std::tuple(2, 65.2), std::tuple('A'));