C++ Ожидание будущего в основном потоке без использования while(true)

Вопрос

Я хочу знать, можно ли ждать в основном потоке без цикла while(1). Запускаю несколько тредов через std::async() и в каждом треде делаю подсчет чисел. После того, как я начну потоки, я хочу получить результаты обратно. Я делаю это с помощью std::future<>.get().

Моя проблема

Когда я получаю результат, я вызываю std::future.get(), который блокирует основной поток, пока не будет выполнен расчет в потоке. Это приводит к некоторому замедлению времени выполнения, если одному потоку требуется значительно больше времени, чем следующему, где вместо этого я мог бы выполнить некоторые вычисления с готовыми результатами, а затем, когда самый медленный поток будет выполнен, у меня, возможно, будет какой-то дополнительный расчет.

Есть ли способ приостановить основной поток до тех пор, пока ЛЮБОЙ из потоков не завершит работу? Я подумал о функции обратного вызова, которая пробуждает основной поток, но я до сих пор не знаю, как бездействовать основную функцию, не делая ее не отвечающей, например, на секунду, и вместо этого не запуская цикл while (true).

Текущий код

#include <iostream>
#include <future>

uint64_t calc_factorial(int start, int number);

int main()
{
    uint64_t n = 1;

    //The user entered number 
    uint64_t number = 0;

    // get the user input
    printf("Enter number (uint64_t): ");
    scanf("%lu", &number);

    std::future<uint64_t> results[4];
    for (int i = 0; i < 4; i++)
    {
        // push to different cores
        results[i] = std::async(std::launch::async, calc_factorial, i + 2, number);
    }

    for (int i = 0; i < 4; i++)
    {
        //retrieve result...I don't want to wait here if one threads needs more time than usual
        n *= results[i].get();
    }
    // print n or the time needed 
    return 0;
}
uint64_t calc_factorial(int start, int number)
{
    uint64_t n = 1;
    for (int i = start; i <= number; i+=4) n *= i;
    return n;
}

Я подготовил фрагмент кода, который работает нормально, я использую GMP Lib для больших результатов, но вместо этого код работает с uint64_t, если вы вводите небольшие числа.

Примечание

Если вы по какой-либо причине уже скомпилировали библиотеку GMP на своем ПК, вы можете заменить каждый uint64_t на mpz_class

Вы можете использовать переменную условия, чтобы разбудить основной поток, как только будет готово каждое из фьючерсов. en.cppreference.com/w/cpp/thread/condition_variable

JohnFilleau 23.12.2020 21:07

Похоже, вы ищете стандартное библиотечное решение для любой сигнальной модели WaitForMultipleObjects из winapi (или аналогичной модели из других бэкэндов). Этого не существует. Вы должны написать свой собственный код (предостережение: я не знаком с модулем параллелизма стандартной библиотеки, начиная с С++ 14, поэтому, если что-то было добавлено в С++ 20, ymmv).

WhozCraig 23.12.2020 21:08

Отвечает ли это на ваш вопрос? Ждете несколько фьючерсов?

Quimby 23.12.2020 21:08

@Куимби вроде как в самом вопросе. Это может быть подход с очередью сохранения потока и условной переменной. Ответ на вопрос слишком прожорлив к процессору

Zacki 23.12.2020 21:14

Какое именно поведение вы хотите, если один из результатов еще недоступен? Можете ли вы объяснить, что вы хотите, чтобы программа делала, если один поток занимает слишком много времени?

JohnFilleau 23.12.2020 21:14

@JohnFilleau Я не хочу, чтобы программа останавливалась и ждала одного потока ... вычисления должны быть объединены вместе, и я хочу, чтобы, если одно объявление выполняется медленно, другие результаты объединялись параллельно.

Zacki 23.12.2020 21:16

Кстати, я изучил большинство предложенных вопросов, прежде чем отправить его, и вопрос @Quimby не появился.

Zacki 23.12.2020 21:17

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

Quimby 23.12.2020 21:19
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
8
989
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я бы подошел к этому несколько иначе.

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

Это делает автоматическим (и тривиальным) то, что вы создаете каждый результат по мере его создания, а не застреваете в ожидании результатов один за другим.

Я сделал цикл while и уже перебирал массив будущего, я не показывал это из соображений сложности, но, как я уже сказал, мне не нравится этот подход. Ваш ответ, кажется, удовлетворяет мои потребности: D

Zacki 23.12.2020 21:20

Из любопытства, как вы справляетесь с отказами в этом случае? Глобальный флаг? Специальное значение очереди?

Quimby 23.12.2020 21:21

@Quimby: немного зависит от типа обработки, но во многих случаях возвращается что-то вроде std::optional<Result>.

Jerry Coffin 23.12.2020 21:45

Спасибо, это кажется самым чистым решением.

Quimby 23.12.2020 22:39

Другие вопросы по теме