C ++ Common std :: make_unique, std :: packaged_task и std :: обещание Проблема - PullRequest
0 голосов
/ 01 сентября 2018

Проблема При создании планировщиков последняя копия или перемещение объекта функции является последним местом, на которое когда-либо ссылался объект функции (рабочий поток). Если бы вы использовали std :: function для хранения функций в планировщике, то любые типы std :: promises или std :: packaged_task или другие подобные перемещения не работают, так как они не могут быть скопированы std :: function.

Аналогичным образом, если вы используете std :: packaged_task в планировщике, это накладывает ненужные накладные расходы, так как для многих задач вообще не требуется std :: future, возвращаемое упакованной задачей.

Распространенным и не лучшим решением является использование std :: shared_ptr <<strong> std :: обещания > или std :: shared_ptr <<strong> std :: packaged_task >, который работает, но это накладывает много накладных расходов.

Решение Make_owner, похожий на make_unique с одним ключевым отличием, копия хода ИЛИ просто передает управление уничтожением объекта. Он в основном идентичен std :: unique_ptr, за исключением того, что он копируемый (он в основном всегда перемещается, даже на копии). Grosss ....

Это означает, что перемещение std :: functions не требует копий std :: shared_ptr, которые требуют подсчета ссылок, а также означает, что при подсчете ссылок значительно меньше накладных расходов и т. Д. Один атомарный указатель на объект будет потребуется, и копия ИЛИ передаст управление. Основным отличием является то, что копия также передает управление, это может быть немного нет-нет с точки зрения строгих правил языка, но я не вижу другого способа обойти это.

Это решение плохое, потому что:

  • Он игнорирует копирование.
  • Отбрасывает const (в конструкторе копирования и в операторе =)

Grrr Это не такое приятное решение, как хотелось бы, поэтому, если кто-нибудь знает другой способ избежать использования общего указателя или только использования packaged_tasks в планировщике, я бы хотел услышать его, потому что я в замешательстве ...

Я довольно недоволен этим решением .... Есть идеи? Я могу повторно реализовать std :: function с симметрией перемещения, но это кажется огромной болью в заднице, и у нее есть свои собственные проблемы с временем жизни объекта (но они уже существуют при использовании std :: function с захватом ссылок).

Некоторые примеры проблемы:

EDIT Обратите внимание, что в целевом приложении я не могу выполнить std :: thread a (std :: move (a)), поскольку потоки планировщика всегда работают, в большинстве случаев они переводятся в состояние сна, никогда не включаются и никогда не останавливаются. В пуле потоков установлено фиксированное количество потоков, я не могу создавать потоки для каждой задачи.

auto proms = std::make_unique<std::promise<int>>();
auto future = proms->get_future();

std::thread runner(std::move(std::function( [prom = std::move(proms)]() mutable noexcept
{
    prom->set_value(80085);
})));

std::cout << future.get() << std::endl;
std::cin.get();

И пример с упакованной_такой

auto pack = std::packaged_task<int(void)>
(   [] 
    {   
        return 1; 
    });
auto future = pack.get_future();

std::thread runner(std::move(std::function( [pack = std::move(pack)]() mutable noexcept
{
    pack();
})));

std::cout << future.get() << std::endl;
std::cin.get();

EDIT

Мне нужно сделать это из контекста планировщика, я не смогу перейти в поток.

Обратите внимание, что вышеприведенное минимально воспроизводимо, std :: async не подходит для моего приложения.

1 Ответ

0 голосов
/ 01 сентября 2018

Основной вопрос: почему вы хотите обернуть лямбду в std::function перед передачей в конструктор std::thread?

Это прекрасно для этого:

std::thread runner([prom = std::move(proms)]() mutable noexcept
{
    prom->set_value(80085);
});

Вы можете найти объяснение того, почему std::function не позволяет хранить лямбду только для перемещения здесь .

Если вы собираетесь передать std::function с обернутой лямбдой какой-то функции вместо:

void foo(std::function<void()> f)
{
    std::thread runner(std::move(f));
    /* ... */
}

foo(std::function<void()>([](){}));

Вы можете сделать это:

void foo(std::thread runner)
{
    /* ... */
}

foo(std::thread([](){}));

Обновление : Это можно сделать по старинке.

std::thread runner([prom_deleter = proms.get_deleter(), prom = proms.release()]() mutable noexcept
{
    prom->set_value(80085);
    // if `proms` deleter is of a `default_deleter` type
    // the next line can be simplified to `delete prom;`
    prom_deleter(prom);
});
...