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

Когда рекомендуется использовать std:: prom по сравнению с другими механизмами std:: thread?

Я пытаюсь установить некоторые эвристики, чтобы помочь мне выбрать подходящий класс std::thread.

Как я понимаю, с самого высокого уровня (простейшего в использовании, но наименее гибкого) до самого низкого уровня мы имеем:

  • std:: async с /std:: future (std:: shared_future) (если вы хотите выполнить однократную асинхронную нисходящую цепочку продлений)
  • std:: packaged_task (если вы хотите назначить производителя, но отложите вызов к потоку)
  • std:: prom (???)

Я думаю, что у меня есть приличное понимание , когда использовать первые два, но я до сих пор неясно о std::promise.

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

std::shared_future - это просто версия, которая позволяет нескольким потребителям.

Если вы хотите связать значение std::future с обратным вызовом производителя, , но хотите отложить фактический вызов до более позднего времени (когда вы связываете задачу с нерестовым потоком), std::packaged_task является правильным выбором. Но теперь, поскольку соответствующий std::future для std::package_task мог бы в общем случае получить доступ к нескольким потокам, нам, возможно, придется позаботиться о том, чтобы использовать std::mutex. Обратите внимание, что при std::async в первом случае нам не нужно беспокоиться о блокировке.

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

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

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

ANSWER

A std::future - странный зверь: в общем случае вы не можете напрямую изменять его значение.

Три производителя, которые могут изменить его значение:

  • std::async через асинхронный обратный вызов, который вернет экземпляр std::future.
  • std::packaged_task, который при передаче в поток вызывает обратный вызов, тем самым обновляя экземпляр std::future, связанный с этим std::packaged_task. Этот механизм позволяет раннее связывание производителя, но более поздний вызов.
  • std::promise, который позволяет изменять связанный с ним std::future через его вызов set_value(). Благодаря этому прямому контролю над мутацией std::future мы должны убедиться, что дизайн является потокобезопасным, если существует несколько производителей (используйте std::mutex как необходимо).

Я думаю ответ SethCarnegie:

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

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

Кроме того, Дэвид Родригес отвечает, также отлично:

Потребительский конец канала связи будет использовать std:: future для извлечения данных из общего состояния, тогда как поток производителя будет использовать std:: prom для записи в общее состояние.

Но как альтернатива, почему бы просто не просто использовать std::mutex в контейнере stl для результатов, а один поток или поток для производителей, чтобы действовать на контейнер? Что использует std::promise, вместо этого, купите меня, помимо некоторой дополнительной удобочитаемости, против stl-контейнера результатов?

В версии std::promise управление выглядит лучше:

  • wait() будет блокироваться в определенном будущем до тех пор, пока не будет получен результат
  • Если есть только один поток производителя, мьютекс не нужен

Следующий google-тест проходит как helgrind, так и drd, подтверждая, что с одним продюсером и с использованием wait() мьютекс не нужен.

TEST

static unsigned MapFunc( std::string const& str ) 
{ 
    if ( str=="one" ) return 1u; 
    if ( str=="two" ) return 2u; 
    return 0u;
}

TEST( Test_future, Try_promise )
{
    typedef std::map<std::string,std::promise<unsigned>>  MAP; 
    MAP          my_map;

    std::future<unsigned> f1 = my_map["one"].get_future();
    std::future<unsigned> f2 = my_map["two"].get_future();

    std::thread{ 
        [ ]( MAP& m )
        { 
            m["one"].set_value( MapFunc( "one" ));
            m["two"].set_value( MapFunc( "two" ));
        }, 
      std::ref( my_map ) 
    }.detach();

    f1.wait();
    f2.wait();

    EXPECT_EQ( 1u, f1.get() );
    EXPECT_EQ( 2u, f2.get() );
}
4b9b3361

Ответ 1

Вместо promise вместо других использовать promise, используйте promise для выполнения future в сочетании с другими. Пример кода в cppreference.com дает пример использования всех четырех:

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

int main()
{
    // future from a packaged_task
    std::packaged_task<int()> task([](){ return 7; }); // wrap the function
    std::future<int> f1 = task.get_future();  // get a future
    std::thread(std::move(task)).detach(); // launch on a thread

    // future from an async()
    std::future<int> f2 = std::async(std::launch::async, [](){ return 8; });

    // future from a promise
    std::promise<int> p;
    std::future<int> f3 = p.get_future();
    std::thread( [](std::promise<int>& p){ p.set_value(9); }, 
                 std::ref(p) ).detach();

    std::cout << "Waiting...";
    f1.wait();
    f2.wait();
    f3.wait();
    std::cout << "Done!\nResults are: "
              << f1.get() << ' ' << f2.get() << ' ' << f3.get() << '\n';
}

печатает

Ожидание... Готово!

Результаты: 7 8 9

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

Легкий способ подумать о том, что вы можете либо установить future, вернув значение, либо используя promise. future не имеет метода set; эта функциональность предоставляется promise. Вы выбираете то, что вам нужно, исходя из того, что позволяет ситуация.

Ответ 2

Если у вас есть два уровня асинхронизации, вам нужно использовать обещание. Например:

void fun()
{
std::promise<int> p;
std::future<int> f = p.get_future();
std::future<void> f2;
auto f3 = std::async([&]{
   // Do some other computation
   f2 = std::async([&]{ p.set_value(42);});
   // Do some other work
});
   // Do some other work
   // Now wait for the result of async work;
std::cout << f.get();
   // Do some other work again 
// Wait for async threads to complete
f3.wait();
f2.wait();
}