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

Путаница о потоках, запущенных std:: async с параметром std:: launch:: async

Я немного запутался в функции std::async.

В спецификации указано: выполняется асинхронная операция "как в новом потоке выполнения" (С++ 11 §30.6.8/11).

Теперь, что это значит?

В моем понимании код

std::future<double> fut = std::async(std::launch::async, pow2, num);

должен запустить функцию pow2 в новом потоке и передать переменную num в поток по значению, а затем в будущем, когда функция будет выполнена, поместите результат в fut (пока функция pow2 имеет такую ​​подпись, как double pow2(double);). Но спецификация заявляет "как будто", что делает все это для меня туманным.

Возникает вопрос:

В этом случае всегда запускается новый поток? Надеюсь, что так. Я имею в виду для меня параметр std::launch::async имеет смысл таким образом, что я явно заявляю, что я действительно хочу создать новый поток.

И код

std::future<double> fut = std::async(std::launch::deferred, pow2, num);

должна сделать ленивую оценку возможной, задерживая вызов функции pow2 до точки, где я пишу что-то вроде var = fut.get();. В этом случае параметр std::launch::deferred должен означать, что я явно заявляю, что я не хочу новый поток, я просто хочу убедиться, что функция вызывается, когда есть необходимость в ее возврате.

Являются ли мои предположения правильными? Если нет, объясните, пожалуйста.

Кроме того, я знаю, что по умолчанию функция вызывается следующим образом:

std::future<double> fut = std::async(std::launch::deferred | std::launch::async, pow2, num);

В этом случае мне сказали, будет ли запущен новый поток или нет, зависит от реализации. Опять же, , что должно означать?

4b9b3361

Ответ 1

Шаблон функции std::async (часть шаблона <future>) используется для запуска (возможно) асинхронной задачи. Он возвращает объект std::future, который в конечном итоге сохранит возвращаемое значение функции параметра std::async.

Когда значение необходимо, мы вызываем get() на экземпляр std::future; это блокирует поток до тех пор, пока будущее не будет готово, а затем вернет значение. std::launch::async или std::launch::deferred можно указать в качестве первого параметра std::async, чтобы указать, как выполняется задание.

  • std::launch::async указывает, что вызов функции должен выполняться по собственной (новой) теме. (Возьмите комментарий пользователя @T.C.).
  • std::launch::deferred указывает, что вызов функции должен быть отложен до тех пор, пока в будущем не будет вызван либо wait(), либо get(). Собственность на будущее может быть перенесена в другой поток, прежде чем это произойдет.
  • std::launch::async | std::launch::deferred указывает, что реализация может выбрать. Это опция по умолчанию (если вы не укажете ее самостоятельно). Он может решить запустить синхронно.

В этом случае всегда запускается новый поток?

От 1. можно сказать, что новый поток всегда запускается.

Являются ли мои предположения [на std:: launch:: отложенные] правильными?

От 2. можно сказать, что ваши предположения верны.

Что это значит? [в связи с запуском или отсутствием нового потока в зависимости от реализации]

От 3., поскольку std::launch::async | std::launch::deferred является параметром по умолчанию, это означает, что реализация функции шаблона std::async решит, будет ли он создавать новый поток или нет. Это связано с тем, что некоторые реализации могут проверять наличие избыточного планирования.

Внимание

Следующий раздел не связан с вашим вопросом, но я думаю, что важно иметь в виду.

В стандарте С++ говорится, что если a std::future содержит последнюю ссылку на общее состояние, соответствующее вызову асинхронной функции, то std:: future destructor должен блокироваться до тех пор, пока поток для асинхронной работы не завершится. Таким образом, экземпляр std::future, возвращаемый std::async, блокирует его деструктор.

void operation()
{
    auto func = [] { std::this_thread::sleep_for( std::chrono::seconds( 2 ) ); };
    std::async( std::launch::async, func );
    std::async( std::launch::async, func );
    std::future<void> f{ std::async( std::launch::async, func ) };
}

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

Первый вызов std::async будет блокироваться в течение 2 секунд, а затем еще 2 секунды блокировки от второго вызова до std::async. Мы можем думать, что последний вызов std::async не блокируется, так как мы сохраняем возвращаемый экземпляр std::future в переменной, но поскольку это локальная переменная, которая уничтожается в конце области действия, она фактически блокирует дополнительные 2 секунды в конце области действия, когда локальная переменная f уничтожена.

Другими словами, вызов функции operation() блокирует любой поток, который он вызывается синхронно в течение приблизительно 6 секунд. Такие требования могут отсутствовать в будущей версии стандарта С++.

Источники информации, которые я использовал для компиляции этих заметок:

С++ Concurrency в действии: Практическая многопоточность, Энтони Уильямс

Сообщение блога Скотта Мейерса: http://scottmeyers.blogspot.ca/2013/03/stdfutures-from-stdasync-arent-special.html

Ответ 2

Я тоже был смущен этим и быстро провел тест в Windows, который показывает, что будущее async будет выполняться в потоках пула потоков ОС. Простое приложение может продемонстрировать это, и в Visual Studio также будут показаны исполняемые потоки с именем "TppWorkerThread".

#include <future>
#include <thread>
#include <iostream>

using namespace std;

int main()
{
    cout << "main thread id " << this_thread::get_id() << endl;

    future<int> f1 = async(launch::async, [](){
        cout << "future run on thread " << this_thread::get_id() << endl;
        return 1;
    });

    f1.get(); 

    future<int> f2 = async(launch::async, [](){
        cout << "future run on thread " << this_thread::get_id() << endl;
        return 1;
    });

    f2.get();

    future<int> f3 = async(launch::async, [](){
        cout << "future run on thread " << this_thread::get_id() << endl;
        return 1;
    });

    f3.get();

    cin.ignore();

    return 0;
}

Результат будет выглядеть так:

main thread id 4164
future run on thread 4188
future run on thread 4188
future run on thread 4188