Поток действительно начинается после вызова std :: future :: get ()?

Я создал цикл для определения std::vector из std::future выполнить мою функцию vector<int> identify и еще один цикл, чтобы получить результаты, позвонив std::future::get() следующее:

for (int i = 0; i < NUM_THREADS; ++i) {
VecString::const_iterator first = dirList.begin() + i*share;
VecString::const_iterator last = i == NUM_THREADS - 1 ? dirList.end() : dirList.begin() + (i + 1)*share;
VecString job(first, last);
futures[i] = async( launch::async, [=]() -> VecInt {
return identify(i, job, make_tuple( bIDList, wIDList, pIDList, descriptor), testingDir, binaryMode, logFile, logFile2 );
} );
}

int correct = 0;
int numImages = 0;
for( int i = 0; i != NUM_THREADS; ++i ) {
VecInt ret = futures[i].get();
correct += ret[0];
numImages += ret[1];
}

Работа состоит в том, чтобы обрабатывать некоторые изображения, и я делю работу примерно поровну между каждым потоком. Я тоже вставляю std::cout в функции, чтобы указать, из какого потока получены результаты.

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

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

2

Решение

Потому что вы используете std::launch::asyncэто до std::async определить, как планировать ваши запросы. В соответствии с cppreference.com:

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

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

Если асинхронной флаг установлен (т.е. policy & std::launch::async != 0), затем async выполняет вызываемый объект f в новом потоке выполнения (со всеми инициализированными локальными потоками), как если бы он был порожден std::thread(std::forward<F>(f), std::forward<Args>(args)...)за исключением того, что если функция f возвращает значение или выдает исключение, она сохраняется в общем состоянии, доступном через std::future этот асинхронный возврат к вызывающей стороне.

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

#include <iostream>
#include <future>
#include <thread>
#include <vector>
#include <chrono>

using namespace std;

int main() {
auto start = chrono::steady_clock::now();
auto timestamp = [start]( ostream & s )->ostream& {
auto now = chrono::steady_clock::now();
auto elapsed = chrono::duration_cast<chrono::microseconds>(now - start);
return s << "[" << elapsed.count() << "us] ";
};

vector<future<int>> futures;
for( int i = 0; i < 5; i++ )
{
futures.emplace_back( async(launch::async,
[=](){
timestamp(cout) << "Launch " << i << endl;
return i;
} ) );
}

this_thread::sleep_for( chrono::milliseconds(100) );

for( auto & f : futures ) timestamp(cout) << "Get " << f.get() << endl;

return 0;
}

Выход (живой пример здесь):

[42us] Launch 4
[85us] Launch 3
[95us] Launch 2
[103us] Launch 1
[109us] Launch 0
[100134us] Get 0
[100158us] Get 1
[100162us] Get 2
[100165us] Get 3
[100168us] Get 4

Эти операции тривиальны, но если у вас есть долгосрочные задачи, вы можете ожидать, что некоторые или все эти задачи все еще могут выполняться при вызове std::future<T>::get(), В этом случае ваша тема будет приостановлена ​​до обещание связано с этим будущее доволен. Кроме того, поскольку асинхронные задачи могут быть объединены, возможно, что некоторые не начнут оценку до тех пор, пока другие не будут завершены.

Если вы используете вместо std::launch::deferred, тогда вы получите ленивую оценку в вызывающем потоке, и поэтому результат будет примерно таким:

[100175us] Launch 0
[100323us] Get 0
[100340us] Launch 1
[100352us] Get 1
[100364us] Launch 2
[100375us] Get 2
[100386us] Launch 3
[100397us] Get 3
[100408us] Launch 4
[100419us] Get 4
[100430us] Launch 5
1

Другие решения

Вообще, в будущем картина get обычно блокируется, пока результат не будет установлен на будущее. Этот результат также может быть исключением. Таким образом, установка результата из другого потока разблокирует get. То же самое происходит, если исключение распространяется на будущее.

Вот ссылка на cppreference.com который описывает это:

Метод get ожидает, пока в будущем не будет действительного результата, и (в зависимости от того, какой шаблон используется) извлекает его. Он эффективно вызывает wait (), чтобы дождаться результата.

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

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

станд :: асинхронной: Не гарантирует асинхронное выполнение. Вот что эталонные состояния:

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

Далее говорится:

Если установлен асинхронный флаг (то есть политика & std :: launch :: async! = 0), тогда async выполняет вызываемый объект f на новый поток исполнения (со всеми инициализированными локальными потоками), как будто порождены std :: thread (std :: forward (f), std :: forward (args) …), за исключением того, что если функция f возвращает значение или выдает исключение он хранится в общем состоянии, доступном через std :: future, который async возвращает вызывающей стороне.

Можете ли вы сначала попробовать запустить версию std::async без какой-либо политики, это должно / может повторно использовать внутренний пул потоков. Если он работает быстрее, то проблема может заключаться в том, что приложение не использует потоки повторно?

Наконец, ссылка на async имеет примечание, в котором указано, когда выполнение может быть синхронным:

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

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

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

Отладка многопоточных приложений немного сложна. Я бы предложил создать только один дополнительный поток и сделать так, чтобы все задания / фьючерсы проходили через него, и посмотреть, есть ли некоторые заблуждения в исполнении. На этом этапе основной поток не будет беспокоить, поскольку он просто ждет результатов.

Вы также можете использовать некоторую библиотеку журналов, которая является поточно-ориентированной (например, Boost Log) и записывать, что там происходит и сколько различных потоков создано std::async путем выхода из идентификатора потока, и если эти потоки используются повторно вообще.

3