Я по наивности полагал, что std::async(std::launch::async) сделает свое дело, но оказалось фигушки
((
std::thread{}.detach() не очень вариант, т.к. долгий и может провалиться.
А надо-то всего лишь выполнить кусочек кода, не ожидая его результата, но используя тред пул. Неужели блин в великом и могучем С++ нельзя это сделать?!
Вариант
static std::future<void> fut;
fut = std::async(DoTask);
компилируется и даже вроде работает, но что-то он мне так попахивает, блин.
Можно ли как-то это сделать
не через жопу более корректно?
на текущий момент этого в стандарте нет
только планируют завозить executor в С++23
так что или асио/буст асио
или что то свое на коленке
можно еще нагуглить
https://stackoverflow.com/questions/40806894/is-there-an-implementation-of-stdasync-which-uses-thread-pool
Здравствуйте, reversecode, Вы писали:
R>или что то свое на коленке
R>можно еще нагуглить
Просто на правах рекламы наколеночный вариант на базе SObjectizer:
#include <so_5/all.hpp>
using namespace std::chrono_literals;
class async_pool_t final {
class executor_t final : public so_5::agent_t {
public:
using task_t = std::function<void()>;
executor_t(context_t ctx) : so_5::agent_t{std::move(ctx)} {
so_subscribe_self().event(
[](const task_t & task) { task(); },
so_5::thread_safe);
}
};
so_5::wrapped_env_t sobj_;
const so_5::mbox_t exec_mbox_;
static auto make_executor(
so_5::environment_t & env,
std::size_t pool_size) {
return env.introduce_coop(
so_5::disp::adv_thread_pool::make_dispatcher(env, pool_size).binder(),
[](so_5::coop_t & coop) {
return coop.make_agent<executor_t>()->so_direct_mbox();
});
}
public:
async_pool_t(std::size_t pool_size)
: sobj_{}
, exec_mbox_{make_executor(sobj_.environment(), pool_size)}
{}
void async(std::function<void()> task) {
so_5::send<executor_t::task_t>(exec_mbox_, std::move(task));
}
};
int main() {
async_pool_t pool{4};
pool.async([] {
std::cout << "First task (250ms)" << std::endl;
std::this_thread::sleep_for(250ms);
});
pool.async([] {
std::cout << "Second task (150ms)" << std::endl;
std::this_thread::sleep_for(150ms);
});
pool.async([] {
std::cout << "Third task (50ms)" << std::endl;
std::this_thread::sleep_for(50ms);
});
pool.async([] {
std::cout << "Fourth task (350ms)" << std::endl;
std::this_thread::sleep_for(350ms);
});
pool.async([] {
std::cout << "Fifth task (150ms)" << std::endl;
std::this_thread::sleep_for(150ms);
});
std::cout << "All tasks started" << std::endl;
}
Здесь создается пул рабочих нитей и один актор, который способен все свои заявки обрабатывать параллельно. Далее каждая новая задача отсылается этому актору в виде объекта
std::function<void()>. Функция main() завершается только когда все отосланные актору заявки будут обработаны.
Если фантазировать дальше, то с учетом наличия в SObjectizer таймеров, можно, при необходимости, сделать так, чтобы задачи запускались спустя какое-то время. Либо, чтобы запускались до истечения какого-то времени (а потом просто игнорировались).
Здравствуйте, Basil2, Вы писали:
B>Вариант
B>B>static std::future<void> fut;
B>fut = std::async(DoTask);
B>
компилируется и даже вроде работает, но что-то он мне так попахивает, блин.
B>Можно ли как-то это сделать не через жопу более корректно?
Это корректный подход, потому что:
If the std::future obtained from std::async is not moved from or bound to a reference, the destructor of the std::future will block at the end of the full expression until the asynchronous operation completes
Кому начхать на то, как будет выполняться, тот сделает std::thread{}.detach()
А остальные управляют ожиданием через время жизни std::future.
{
SomeClass abc;
...
auto res = std::async(DoTask);
...
здесь будет ожидание окончания DoTask в деструкторе std::future
и только потом уже вызов SomeClass::~SomeClass()
}
Если в ходе работы программы стало ясно, что нет надобности ждать окончания DoTask, то этот res можно подвергнуть тому самому «moved from or bound to a reference» — в соответствующей ветке условного оператора.