I'm trying to write a function TimeoutFunction, which calls an other function e.x. printf, if the function TimeoutFunction wasn't called again within x seconds. If it was called again, the timeout should be resetted.
This code for example:
void TimeoutFunction(string MyString)
{
//Wait 5 seconds and then call printf(MyString)
}
int main()
{
TimeoutFunction("A");
Sleep(4);
TimeoutFunction("B");
Sleep(6);
TimeoutFunction("C");
Sleep(10);
TimeoutFunction("D");
Sleep(2);
TimeoutFunction("E");
}
would print:
BCE or at least BC
The Parameter MyString isn't necessary, but I added it to visualize it.
I wrote some code which is not nice, but it works as i excepted
#include <thread>
#include <windows.h>
bool running;
unsigned long StartTime;
unsigned int WaitTime = 500;
const char* PrintString;
std::thread InternThread;
void MyPrint()
{
while (running)
{
auto WaitedTime = GetTickCount() - StartTime;
if (WaitedTime > WaitTime)
{
printf(PrintString);
break;
}
Sleep(10);
}
}
void TimeoutFunction(const char* MyString)
{
StartTime = GetTickCount();
PrintString = MyString;
running = false;
if (InternThread.joinable())
{
InternThread.join();
}
running = true;
InternThread = std::thread(MyPrint);
}
int main()
{
TimeoutFunction("A");
Sleep(400);
TimeoutFunction("B");
Sleep(600);
TimeoutFunction("C");
Sleep(1000);
TimeoutFunction("D");
Sleep(200);
TimeoutFunction("E");
InternThread.join();
return 0;
}
If someone has a nicer code, you're welcome.
I've coded up equivalent hopefully portable code based on your self-answer.
But first:
The technique you use in your own answer is smart, ensuring that either the main thread, or the worker thread, is accessing the common shared variables at any time. This was not my first idea of how to do this, i.e. it was not obvious to me, and it makes the code both simpler and more efficient than using a mutex for ensuring exclusive access to these variables. However, there are two synchronization problems with the way you've coded it up:
The bool flag running needs to be made thread-safe.
If the flag isn't made thread safe, then changes made in one thread (e.g. the main thread) may just make it out to some cache, but not all the way out to main memory, and likewise, the other thread's checking may just check a cache, and not directly main memory. Three possibilites are std::atomic<bool>, std::atomic_flag (less convenient but guaranteed lock free) and, third, using an extra std::mutex, e.g. combined with std::unique_lock.
In the TimeoutFunction function the shared state updates need to made after joining the thread.
For perfection the GetTickCount result then should be stored in a local variable before waiting on the thread to join.
For the standard C++ hopefully-portable code the main function is like this (doing almost exactly the same as in your posted answer):
main.cpp:
#include <stdio.h> // printf
#include <Delayed_action.hpp> // my::(Delayed_action, sleep)
// Alternative to defining lots of small lambdas, a special case functor:
struct Print
{
char const* s;
void operator()() const { printf( "%s", s ); }
Print( char const* const a_literal ): s( a_literal ) {}
};
auto main()
-> int
{
using my::Delayed_action; using my::sleep;
using namespace std::literals; // ms
Delayed_action da;
da.set_action( Print{ "A" } );
sleep( 400ms );
da.set_action( Print{ "B" } );
sleep( 600ms );
da.set_action( Print{ "C" } );
sleep( 1000ms );
da.set_action( Print{ "D" } );
sleep( 200ms );
da.set_action( Print{ "E" } );
da.wait_for_action_completed();
printf( "\n" );
}
The two main abstractions here, that support reuse, are
Putting the thread communication state in an object instead of globals.
Parameterizing the action, instead of a hardcoded action.
The implementation of Delayed_action uses some general support stuff:
cppx-class-kinds.hpp:
#pragma once
namespace cppx {
class No_copy
{
private:
auto operator=( No_copy const& ) -> No_copy& = delete;
No_copy( No_copy const& ) = delete;
public:
auto operator=( No_copy&& ) -> No_copy& { return *this; }
No_copy() {}
No_copy( No_copy&& ) {}
};
class No_move
{
private:
auto operator=( No_move&& ) -> No_move& = delete;
No_move( No_move&& ) = delete;
public:
auto operator=( No_move const& ) -> No_move& { return *this; }
No_move() {}
No_move( No_move const& ) {}
};
class No_copy_or_move
: public No_copy
, public No_move
{};
} // namespace cppx
cppx-threading.hpp:
#pragma once
#include <cppx-class-kinds.hpp> // cppx::No_copy_or_move
#include <atomic> // std::atomic
#include <chrono> // std::chrono::milliseconds
#include <thread> // std::thread
namespace cppx {
namespace this_thread = std::this_thread;
inline namespace std_aliases {
using Milliseconds = std::chrono::milliseconds;
using Steady_clock = std::chrono::steady_clock;
using Time_point = std::chrono::time_point<Steady_clock>;
using Thread = std::thread;
}
inline void sleep( Milliseconds const duration )
{
this_thread::sleep_for( duration );
}
// Syntactic sugar for std::atomic_flag:
// • boolean assignment
// • default init to false.
// std::atomic_flag is guaranteed lock free, as opposed to std::atomic<bool>.
// Cost: there's no way to check the value except by setting it to true.
class Atomic_flag
: public No_copy_or_move
{
private:
std::atomic_flag flag_ = ATOMIC_FLAG_INIT; // Initialized to false.
public:
void clear() { flag_.clear(); }
auto test_and_set() -> bool
{
bool const previous_value = flag_.test_and_set();
return previous_value;
}
void set() { test_and_set(); }
void operator=( bool const should_be_set )
{
if( should_be_set ) set(); else clear();
}
Atomic_flag() {}
};
} // namespace cppx
With that wrapping and renaming of the standard library's things, the standard library based re-implementation of your idea, the Delayed_action class, can look like this:
Delayed_action.hpp:
#pragma once
#include <cppx-class-kinds.hpp> // cppx::No_copy_or_move
#include <cppx-threading.hpp> // cppx::(Atomic_flag, sleep)
#include <functional> // std::(function, ref)
#include <utility> // std::move
namespace my {
using namespace cppx::std_aliases;
using namespace std::literals;
using cppx::Atomic_flag;
using cppx::No_copy_or_move;
using cppx::sleep;
using std::move;
using std::ref;
using Action = std::function<void()>;
class Delayed_action
: public No_copy_or_move
{
private:
struct Parameters
{
Atomic_flag run;
Action action;
Time_point when;
};
static void polling( Parameters& parameters )
{
for( ;; )
{
if( not parameters.run.test_and_set() )
{
return;
}
else if( Steady_clock::now() >= parameters.when )
{
parameters.action();
return;
}
sleep( 10ms );
}
}
private:
Parameters parameters_;
Thread worker_;
void join_worker_thread()
{
if( worker_.joinable() )
{
worker_.join();
}
}
void end_worker_thread()
{
parameters_.run = false;
join_worker_thread();
}
public:
static auto default_delay() -> Milliseconds { return 500ms; }
void set_action( Action action, Milliseconds const delay = default_delay() )
{
Time_point const when = Steady_clock::now() + delay;
end_worker_thread();
parameters_.action = move( action );
parameters_.when = when;
parameters_.run = true;
worker_ = Thread( &polling, ref( parameters_ ) );
}
void wait_for_action_completed() { join_worker_thread(); }
~Delayed_action() { end_worker_thread(); }
Delayed_action() {}
Delayed_action( Action action, Milliseconds const delay = default_delay() )
{
set_action( move( action ), delay );
}
};
} // namespace my
Related
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.
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";
}
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'));
Demo.
In C++11 have a list called jobs, in which I want to delete all jobs with stopped flag being true, so I wrote:
auto job = jobs.begin();
while (job != jobs.end()) {
if (!job->stopped) {
job = jobs.erase(job)
} else {
++job;
}
}
But someone took a look at my code and said it's wrong which I doin't understand why?
If do not take into account the typo relative to the missing semicolon in this statement
job = jobs.erase(job)
^^^
and the second typo in this condition
if (!job->stopped) {
^^^^
which should be written like
if ( job->stopped) {
(that is you need to remove as you wrote all jobs with the set flag stopped) your code is correct but is redundant.
You could just write
jobs.remove_if( []( const auto &job ) { return job.stopped; } );
or
jobs.remove_if( []( const JobEntry &job ) { return job.stopped; } );
if this statement is called within a member function.
Edit: Here is a demonstrative program that uses your class declarations.
#include <iostream>
#include <string>
#include <list>
#include <ctime>
typedef int pid_t;
class JobsList {
public:
class JobEntry {
public:
pid_t pid, jid;
std::string cmd;
time_t in_time;
bool stopped;
JobEntry( int pid, int jid, const std::string &cmd, bool stopped )
:pid( pid ), jid( jid ), cmd( cmd ), stopped( stopped )
{}
// TODO: Add your data members
bool operator<( JobEntry const &tmp ) const {
return jid < tmp.jid;
}
bool operator==( JobEntry const &tmp ) const {
return jid == tmp.jid;
}
};
std::list<JobEntry> jobs;
};
int main()
{
JobsList jobs_list =
{
{
{ 1, 1, "first", false },
{ 2, 2, "second", true }
}
};
std::cout << jobs_list.jobs.size() << '\n';
jobs_list.jobs.remove_if( []( const auto &job ) { return job.stopped; } );
std::cout << jobs_list.jobs.size() << '\n';
}
I only introduced for simplicity this typedef
typedef int pid_t;
and changed a parameter declaration in the constructor
JobEntry( int pid, int jid, const std::string &cmd, bool stopped )
^^^^^^^
I wrote a class to share a limited number of resources (for instance network interfaces) between a larger number of threads. The resources are pooled and, if not in use, they are borrowed out to the requesting thread, which otherwise waits on a condition_variable.
Nothing really exotic: apart for the fancy scoped_lock which requires c++17, it should be good old c++11.
Both gcc10.2 and clang11 compile the test main fine, but while the latter produces an executable which does pretty much what expected, the former hangs without consuming CPU (deadlock?).
With the help of https://godbolt.org/ I tried older versions of gcc and also icc (passing options -O3 -std=c++17 -pthread), all reproducing the bad result, while even there clang confirms the proper behavior.
I wonder if I made a mistake or if the code triggers some compiler misbehavior and in case how to work around that.
#include <iostream>
#include <vector>
#include <stdexcept>
#include <mutex>
#include <condition_variable>
template <typename T>
class Pool {
///////////////////////////
class Borrowed {
friend class Pool<T>;
Pool<T>& pool;
const size_t id;
T * val;
public:
Borrowed(Pool & p, size_t i, T& v): pool(p), id(i), val(&v) {}
~Borrowed() { release(); }
T& get() const {
if (!val) throw std::runtime_error("Borrowed::get() this resource was collected back by the pool");
return *val;
}
void release() { pool.collect(*this); }
};
///////////////////////////
struct Resource {
T val;
bool available = true;
Resource(T v): val(std::move(v)) {}
};
///////////////////////////
std::vector<Resource> vres;
size_t hint = 0;
std::condition_variable cv;
std::mutex mtx;
size_t available_cnt;
public:
Pool(std::initializer_list<T> l): available_cnt(l.size()) {
vres.reserve(l.size());
for (T t: l) {
vres.emplace_back(std::move(t));
}
std::cout << "Pool has size " << vres.size() << std::endl;
}
~Pool() {
for ( auto & res: vres ) {
if ( ! res.available ) {
std::cerr << "WARNING Pool::~Pool resources are still in use\n";
}
}
}
Borrowed borrow() {
std::unique_lock<std::mutex> lk(mtx);
cv.wait(lk, [&](){return available_cnt > 0;});
if ( vres[hint].available ) {
// quick path, if hint points to an available resource
std::cout << "hint good" << std::endl;
vres[hint].available = false;
--available_cnt;
Borrowed b(*this, hint, vres[hint].val);
if ( hint + 1 < vres.size() ) ++hint;
return b; // <--- gcc seems to hang here
} else {
// full scan to find the available resource
std::cout << "hint bad" << std::endl;
for ( hint = 0; hint < vres.size(); ++hint ) {
if ( vres[hint].available ) {
vres[hint].available = false;
--available_cnt;
return Borrowed(*this, hint, vres[hint].val);
}
}
}
throw std::runtime_error("Pool::borrow() no resource is available - internal logic error");
}
void collect(Borrowed & b) {
if ( &(b.pool) != this )
throw std::runtime_error("Pool::collect() trying to collect resource owned by another pool!");
if ( b.val ) {
b.val = nullptr;
{
std::scoped_lock<std::mutex> lk(mtx);
hint = b.id;
vres[hint].available = true;
++available_cnt;
}
cv.notify_one();
}
}
};
///////////////////////////////////////////////////////////////////
#include <thread>
#include <chrono>
int main() {
Pool<std::string> pool{"hello","world"};
std::vector<std::thread> vt;
for (int i = 10; i > 0; --i) {
vt.emplace_back( [&pool, i]()
{
auto res = pool.borrow();
std::this_thread::sleep_for(std::chrono::milliseconds(i*300));
std::cout << res.get() << std::endl;
}
);
}
for (auto & t: vt) t.join();
return 0;
}
You're running into undefined behavior since you effectively relock an already acquired lock. With MSVC I obtained a helpful callstack to distinguish this. Here is a working fixed example (I suppose, works now for me, see the changes within the borrow() method, might be further re-designed since locking inside a destructor might be questioned):
#include <iostream>
#include <vector>
#include <stdexcept>
#include <mutex>
#include <condition_variable>
template <typename T>
class Pool {
///////////////////////////
class Borrowed {
friend class Pool<T>;
Pool<T>& pool;
const size_t id;
T * val;
public:
Borrowed(Pool & p, size_t i, T& v) : pool(p), id(i), val(&v) {}
~Borrowed() { release(); }
T& get() const {
if (!val) throw std::runtime_error("Borrowed::get() this resource was collected back by the pool");
return *val;
}
void release() { pool.collect(*this); }
};
///////////////////////////
struct Resource {
T val;
bool available = true;
Resource(T v) : val(std::move(v)) {}
};
///////////////////////////
std::vector<Resource> vres;
size_t hint = 0;
std::condition_variable cv;
std::mutex mtx;
size_t available_cnt;
public:
Pool(std::initializer_list<T> l) : available_cnt(l.size()) {
vres.reserve(l.size());
for (T t : l) {
vres.emplace_back(std::move(t));
}
std::cout << "Pool has size " << vres.size() << std::endl;
}
~Pool() {
for (auto & res : vres) {
if (!res.available) {
std::cerr << "WARNING Pool::~Pool resources are still in use\n";
}
}
}
Borrowed borrow() {
std::unique_lock<std::mutex> lk(mtx);
while (available_cnt == 0) cv.wait(lk);
if (vres[hint].available) {
// quick path, if hint points to an available resource
std::cout << "hint good" << std::endl;
vres[hint].available = false;
--available_cnt;
Borrowed b(*this, hint, vres[hint].val);
if (hint + 1 < vres.size()) ++hint;
lk.unlock();
return b; // <--- gcc seems to hang here
}
else {
// full scan to find the available resource
std::cout << "hint bad" << std::endl;
for (hint = 0; hint < vres.size(); ++hint) {
if (vres[hint].available) {
vres[hint].available = false;
--available_cnt;
lk.unlock();
return Borrowed(*this, hint, vres[hint].val);
}
}
}
throw std::runtime_error("Pool::borrow() no resource is available - internal logic error");
}
void collect(Borrowed & b) {
if (&(b.pool) != this)
throw std::runtime_error("Pool::collect() trying to collect resource owned by another pool!");
if (b.val) {
b.val = nullptr;
{
std::scoped_lock<std::mutex> lk(mtx);
hint = b.id;
vres[hint].available = true;
++available_cnt;
cv.notify_one();
}
}
}
};
///////////////////////////////////////////////////////////////////
#include <thread>
#include <chrono>
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int main()
{
try
{
Pool<std::string> pool{ "hello","world" };
std::vector<std::thread> vt;
for (int i = 10; i > 0; --i) {
vt.emplace_back([&pool, i]()
{
auto res = pool.borrow();
std::this_thread::sleep_for(std::chrono::milliseconds(i * 300));
std::cout << res.get() << std::endl;
}
);
}
for (auto & t : vt) t.join();
return 0;
}
catch(const std::exception& e)
{
std::cout << "exception occurred: " << e.what();
}
return 0;
}
Locking destructor coupled with missed NRVO caused the issue (credits to Secundi for pointing this out in the comments).
If the compiler skips NRVO, the few lines below if will call the destructor of b. The destructor tries to acquire the mutex before this gets released by the unique_lock, resulting in a deadlock.
Borrowed b(*this, hint, vres[hint].val);
if ( hint + 1 < vres.size() ) ++hint;
return b; // <--- gcc seems to hang here
It is of crucial importance here to avoid destroying b. In fact, even if manually releasing the unique_lock before returning will avoid the deadlock, the destructor of b will mark the pooled resource as available, while this is just being borrowed out, making the code wrong.
A possible fix consists in replacing the lines above with:
const auto tmp = hint;
if ( hint + 1 < vres.size() ) ++hint;
return Borrowed(*this, tmp, vres[tmp].val);
Another possibility (which does not exclude the former) is to delete the (evil) copy ctor of Borrowed and only provide a move ctor:
Borrowed(const Borrowed &) = delete;
Borrowed(Borrowed && b): pool(b.pool), id(b.id), val(b.val) { b.val = nullptr; }
I am trying to implement simple thread pool using boost library.
Here is code:
//boost::asio::io_service ioService;
//boost::thread_group pool;
//boost::asio::io_service::work* worker;
ThreadPool::ThreadPool(int poolSize /*= boost::thread::hardware_concurrency()*/)
{
if (poolSize >= 1 && poolSize <= boost::thread::hardware_concurrency())
threadAmount = poolSize;
else
threadAmount = 1;
worker = NULL;
}
ThreadPool::~ThreadPool()
{
if (worker != NULL && !ioService.stopped())
{
_shutdown();
delete worker;
worker = NULL;
}
}
void ThreadPool::start()
{
if (worker != NULL)
{
return;
}
worker = new boost::asio::io_service::work(ioService);
for (int i = 0; i < threadAmount; ++i)
{
pool.create_thread(boost::bind(&boost::asio::io_service::run, &ioService));
}
}
template<class F, class...Args>
void ThreadPool::execute(F f, Args&&... args)
{
ioService.post(boost::bind(f, std::forward<Args>(args)...));
}
void ThreadPool::shutdown()
{
pool.interrupt_all();
_shutdown();
}
void ThreadPool::join_all()
{
// wait for all threads before continue
// in other words - barier for all threads when they finished all jobs
// and to be able re-use them in futur.
}
void ThreadPool::_shutdown()
{
ioService.reset();
ioService.stop();
}
In my program i assign to thread pool some tasks that needs to be done, and going further with main thread. At some point i need to wait for all threads to finished all tasks before i could proceed calculations. Is there any way to do this ?
Thanks a lot.
As others have pointed out, the main culprit is the work instance.
I'd much simplify the interface (there's really no reason to split shutdown into shutdown, _shutdown, join_all and some random logic in the destructor as well. That just makes it hard to know what responsibility is where.
The interface should be a Pit Of Success - easy to use right, hard to use wrong.
At the same time it makes it much easier to implement it correctly.
Here's a first stab:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/thread.hpp>
namespace ba = boost::asio;
struct ThreadPool {
ThreadPool(unsigned poolSize = boost::thread::hardware_concurrency());
~ThreadPool();
void start();
template <typename F, typename... Args>
void execute(F f, Args&&... args) {
ioService.post(std::bind(f, std::forward<Args>(args)...));
}
private:
unsigned threadAmount;
ba::io_service ioService;
boost::thread_group pool;
std::unique_ptr<ba::io_service::work> work;
void shutdown();
};
ThreadPool::ThreadPool(
unsigned poolSize /*= boost::thread::hardware_concurrency()*/) {
threadAmount = std::max(1u, poolSize);
threadAmount = std::min(boost::thread::hardware_concurrency(), poolSize);
}
ThreadPool::~ThreadPool() {
shutdown();
}
void ThreadPool::start() {
if (!work) {
work = std::make_unique<ba::io_service::work>(ioService);
for (unsigned i = 0; i < threadAmount; ++i) {
pool.create_thread(
boost::bind(&ba::io_service::run, &ioService));
}
}
}
void ThreadPool::shutdown() {
work.reset();
pool.interrupt_all();
ioService.stop();
pool.join_all();
ioService.reset();
}
#include <iostream>
using namespace std::chrono_literals;
int main() {
auto now = std::chrono::high_resolution_clock::now;
auto s = now();
{
ThreadPool p(10);
p.start();
p.execute([] { std::this_thread::sleep_for(1s); });
p.execute([] { std::this_thread::sleep_for(600ms); });
p.execute([] { std::this_thread::sleep_for(400ms); });
p.execute([] { std::this_thread::sleep_for(200ms); });
p.execute([] { std::this_thread::sleep_for(10ms); });
}
std::cout << "Total elapsed: " << (now() - s) / 1.0s << "s\n";
}
Which on most multi-core systems will print something like on mine:
Total elapsed: 1.00064s
It looks like you had an error in calculating threadAmount where you'd take 1 if poolSize was more than hardware_concurrency.
To be honest, why have the bind in the implementation? It really doesn't add a lot, you can leave it up to the caller, and they can choose whether they use bind, and if so, whether it's boost::bind, std::bind or some other way of composing calleables:
template <typename F>
void execute(F f) { ioService.post(f); }
You're missing exception handling around io_service::run calls (see Should the exception thrown by boost::asio::io_service::run() be caught?).
If you're using recent boost version, you can use the newer io_context and thread_pool interfaces, greatly simplifying things:
Live On Coliru
#include <boost/asio.hpp>
struct ThreadPool {
ThreadPool(unsigned poolSize)
: pool(std::clamp(poolSize, 1u, std::thread::hardware_concurrency()))
{ }
template <typename F>
void execute(F f) { post(pool, f); }
private:
boost::asio::thread_pool pool;
};
This still has 99% of the functionality¹, but in 10 LoC.
In fact, the class has become a trivial wrapper, so we could just write:
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
using namespace std::chrono_literals;
using C = std::chrono::high_resolution_clock;
static void sleep_for(C::duration d) { std::this_thread::sleep_for(d); }
int main() {
auto s = C::now();
{
boost::asio::thread_pool pool;
post(pool, [] { sleep_for(1s); });
post(pool, [] { sleep_for(600ms); });
// still can bind if you want
post(pool, std::bind(sleep_for, 400ms));
post(pool, std::bind(sleep_for, 200ms));
post(pool, std::bind(sleep_for, 10ms));
//pool.join(); // implicit in destructor
}
std::cout << "Total elapsed: " << (C::now() - s) / 1.0s << "s\n";
}
Main difference is the default pool size: it is 2*hardware concurrency (but also calculated more safely, because not all platforms have a reliable hardware_concurrency() - it could be zero, e.g.).
¹ It doesn't currently exercise interruptions points
Given that there are std::future::wait_for/until(), I don't see why there is no std::future::try_wait(). I'm currently writing a producer-consumer example, and I want to use std::future as a convenient way to signal the consumer threads to return. My consumer code is like
void consume(std::future<void>& stop) {
while (!stop.try_wait()) { // alas, no such method
// try consuming an item in queue
}
}
I'm thinking to simulate try_wait() with a zero-duration wait_for() which is really ugly. As a side question: any other convenient ways to signal the consumer threads to return?
std::experimental::future has a .is_ready() and .then( F ) methods added to it.
is_ready is probably your try_wait (without a timeout).
wait_for, as noted, gives you the functionality of try_wait in practice.
std::future is not designed as a signaling mechanism even if it can be used as one. If you want a signaling mechansim, create one using a condition variable, mutex, and state that stores the state of the signals (possibly combining them).
struct state {
bool stop = false;
unsigned some_value = 7;
friend auto as_tie( state const& s ) {
return std::tie(s.stop, s.some_value);
}
friend bool operator==( state const& lhs, state const& rhs ) {
return as_tie(lhs)==as_tie(rhs);
}
};
template<class State, class Cmp=std::equal<State>>
struct condition_state {
// gets a copy of the current state:
State get_state() const {
auto l = lock();
return state;
}
// Returns a state that is different than in:
State next_state(State const& in) const {
auto l = lock();
cv.wait( l, [&]{ return !Cmp{}(in, state); } );
return state;
}
// runs f on the state if it changes from old.
// does this atomically in a mutex, so be careful.
template<class F>
auto consume_state( F&& f, State old ) const {
auto l = lock();
cv.wait( l, [&]{ return !Cmp{}(old, state); } );
return std::forward<F>(f)( state );
}
// runs f on the state if it changes:
template<class F>
auto consume_state( F&& f ) const {
return consume_state( std::forward<F>(f), state );
}
// calls f on the state, then notifies everyone to check if
// it has changed:
template<class F>
void change_state( F&& f ) {
{
auto l = lock();
std::forward<F>(f)( state );
}
cv.notify_all();
}
// Sets the value of state to in
void set_state( State in ) {
change_state( [&](State& state) {
state = std::move(in);
} );
}
private:
auto lock() const { return std::unique_lock<std::mutex>(m); }
mutable std::mutex m;
std::condition_variable cv;
State state;
};
For an example, suppose our State was a vector of ready tasks and a bool saying "abort":
struct tasks_todo {
std::deque< std::function<void()> > todo;
bool abort = false;
friend bool operator==()( tasks_todo const& lhs, tasks_todo const& rhs ) {
if (lhs.abort != rhs.abort) return false;
if (lhs.todo.size() != rhs.todo.size()) return false;
return true;
}
};
then we can write our queue as follows:
struct task_queue {
void add_task( std::function<void()> task ) {
tasks.change_state( [&](auto& tasks) { tasks.todo.push_back(std::move(task)); } );
}
void shutdown() {
tasks.change_state( [&](auto& tasks) { tasks.abort = true; } );
}
std::function<void()> pop_task() {
return tasks.consume_state(
[&](auto& tasks)->std::function<void()> {
if (tasks.abort) return {};
if (tasks.todo.empty()) return {}; // should be impossible
auto r = tasks.front();
tasks.pop_front();
return r;
},
{} // non-aborted empty queue
);
}
private:
condition_state<task_todo> tasks;
};
or somesuch.
Since std::future::wait_for is not available, one can specify own timeout routine as shown in the snippet:
void even(int n,promise<bool> p)
{
this_thread::sleep_for(chrono::milliseconds(500ms)); //set milliseconds(10ms) to display result
p.set_value( n%2 == 0?true:false);
}
int main()
{
promise<bool> p;
future<bool> f =p.get_future();
int n = 100;
std::chrono::system_clock::time_point tp1 = std::chrono::system_clock::now() ;
thread t([&](){ even(n,move(p)); });
auto span = std::chrono::milliseconds(200ms);
std::future_status s;
do
{
s =f.wait_for(std::chrono::seconds(0));
// do something
}
while( std::chrono::system_clock::now() < (tp1 + span) );
if( s==future_status::ready)
std::cout << "result is " << (f.get()? "Even": "Odd") << '\n';
else
std::cout << "timeout " << '\n';
t.join();
}