Подтвердить что ты не робот

Всегда ли асинхронно использует другой поток/ядро ​​/процесс в С++?

Как я знаю, async выполняет функцию в другом потоке/процессе/ядре и не блокирует основной поток, но всегда ли это так?

У меня есть следующий код:

async(launch::async,[]()
{
    Sleep(1000);
    puts("async");
});
puts("main");

Он печатает async main, значит ли это, что основной поток ожидает завершения async?

Если я перехожу к следующему:

auto f = async(launch::async,[]() // add "auto f = "
{
    Sleep(1000);
    puts("async");
});
puts("main");

Он печатает main async. Похоже, что main не ждет завершения async.

4b9b3361

Ответ 1

Как я знаю, async выполняет функцию в другом потоке/процессе/ядре и не блокирует основной поток, но всегда ли это происходит?

std::async гарантированно исполняется в отдельном потоке, только если std::launch::async передается как первый аргумент:

  • std::launch::async: запускается новый поток для выполнения задачи асинхронно
  • std::launch::deferred задача выполняется в вызывающем потоке при первом запросе его результата (ленивая оценка)

Политика запуска по умолчанию std::launch::async | std::launch::deferred.


std::async возвращает std::future. std::future destructor будет блокироваться, только если будущее было возвращено с std::async:

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


  • В первом фрагменте кода вы создаете выражение rvalue , которое немедленно уничтожается, поэтому "async" будет напечатано до "main".

    • Асинхронная анонимная функция создается и запускается.

    • Асинхронная анонимная функция уничтожается.

      • main выполнение блокируется до завершения функции.

      • "async".

    • main резюме продолжается.

      • "main".

  • Во втором фрагменте кода вы создаете выражение lvalue, время жизни которого привязано к переменной f. f будет уничтожен в конце области main - поэтому "main" будет напечатан до "async" из-за Delay(1000).

    • Асинхронная анонимная функция создается и запускается.

      • Там Delay(1000), который немедленно откладывает "async".
    • main выполнение продолжается.

      • "main".
    • Конец области main.

    • Асинхронная анонимная функция уничтожается.

      • main выполнение блокируется до завершения функции.

      • "async".

Ответ 2

Он печатает async main, значит ли это, что основной поток ожидает завершения async?

Да, но это происходит потому, что вы не можете вернуть возвращаемое будущее из async. async отличается тем, что возвращенный из него future блокирует в деструкторе до тех пор, пока поток не завершится. Поскольку вы не захватили возвращенный future

async(launch::async,[]()
{
    Sleep(1000);
    puts("async");
});

должен завершиться до того, как будет выполнен прогресс в текущем потоке, так как возвращенный future будет уничтожен в конце выражения.

Он печатает main async. Похоже, что main не ждет завершения async.

Это то, что вам действительно нужно, когда вы вызываете async. Поскольку вы захватили будущее, ваш основной поток можно продолжить, пока задача asynchronis будет завершена. Поскольку у вас есть задержка в том, что поток main будет печатать перед потоком.

Ответ 3

Если вы передаете std::launch::async, тогда std::async должен запустить задачу так, как если бы она выполнялась в своем потоке.

Единственное понятие потоковой передачи в С++ - std::thread.

std::async возвращает a std::future с уникальным свойством; если он уничтожен, он блокирует выполнение задачи, хранящейся в std::async. Это ловушки, когда вы не можете захватить возвращаемое значение; возвращенный std::future является неназванным временным, который возникает и уничтожается на "конце этой строки".

Это уничтожение ожидает завершения задачи async.

В том случае, когда вы его сохраняете, эта задержка ждет, пока не будет уничтожена переменная f, которая находится в конце main, которая после печати. ​​

Обратите внимание, что хотя бы одна из основных реализаций С++ 11, MSVC 2015 и 2017, имеет в лучшем случае минимально совместимый std::async, который использует пул потоков вместо новых потоков. Этот пул потоков означает, что один из множества длительных вызовов async может отсрочить выполнение других вызовов async.

Использование пула потоков является законным (если оно воссоздает любые локаторы потоков), но оно должно стараться избегать голодания и создавать новые потоки, если все существующие потоки заняты "слишком долго".

Он незначительно совместим, потому что стандарт только утверждает, что потоки "должны" продвигаться вперед. Темы, которые никогда не прогрессируют по случайным причинам, являются законными в С++; и в смысле вы можете утверждать, что это то, что std::async эмулирует в этих случаях, таким образом передавая тест as-if.

Ответ 4

Это связано с тем, что деструктор std::future (возвращаемый из std::async) ожидает завершения своей задачи.

В первом фрагменте кода временный объект std::future возвращаемый из std::async, уничтожается в конце оператора, потому что, как написано в https://en.cppreference.com/w/cpp/language/lifetime

Все временные объекты уничтожаются как последний шаг в оценке полного выражения, которое (лексически) содержит точку, в которой они были созданы

Следовательно, перед выполнением следующего оператора деструктор объекта std::future блокируется до тех пор, пока задача не будет завершена, что означает выполнение puts("async") перед выполнением puts("main").

Однако во втором фрагменте кода возвращаемое значение std :: async перемещается в локальный объект, который уничтожается при выходе из области действия. Следовательно, строка с async выполнением выполняется без блока, а puts("main") выполняются до того, как puts("async") (который блокируется вызовом Sleep), как и ожидалось.

Это поведение объясняется в https://en.cppreference.com/w/cpp/thread/async как:

Если std :: future, полученный из std :: async, не перемещен или не связан со ссылкой, деструктор std :: future будет блокироваться в конце полного выражения до завершения асинхронной операции, по существу создавая код, такой как следующее синхронно:

std::async(std::launch::async, []{ f(); }); // temporary dtor waits for f()
std::async(std::launch::async, []{ g(); }); // does not start until f() completes

В пункте 38 книги " Эффективное современное" C++ это выражается как:

Деструктор для последнего будущего, ссылающийся на общее состояние для неотложенной задачи, запускаемой через блоки std :: async до тех пор, пока задача не завершится. По сути, деструктор для такого будущего выполняет неявное соединение с потоком, в котором выполняется асинхронно выполняемая задача.