Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Название:Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-448-1
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ краткое содержание
Книга «Параллельное программирование на С++ в действии» не предполагает предварительных знаний в этой области. Вдумчиво читая ее, вы научитесь писать надежные и элегантные многопоточные программы на С++11. Вы узнаете о том, что такое потоковая модель памяти, и о том, какие средства поддержки многопоточности, в том числе запуска и синхронизации потоков, имеются в стандартной библиотеке. Попутно вы познакомитесь с различными нетривиальными проблемами программирования в условиях параллелизма.
Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Листинг 4.7.Передача аргументов функции, заданной в std::async
#include
#include
struct X {
void foo(int, std::string const&);
std::string bar(std::string const&);
};
│
Вызывается
X x; │
p->foo(42,"hello"),
auto f1 = std::async(&X::foo, &x, 42, "hello");←┘
где p=&x
auto f2 = std::async(&X::bar, x, "goodbye");←┐
вызывается
│
tmpx.bar("goodbye"),
struct Y { │
где tmpx — копия x
double operator()(double);
}; │
Вызывается tmpy(3.141),
│
где tmpy создается
Y y; │
из Y перемещающим
auto f3 = std::async(Y(), 3.141)←┘
конструктором
auto f4 = std::async(std::ref(y), 2.718);←
Вызывается y(2.718)
X baz(X&);
std::async(baz, std::ref(x); ←
Вызывается baz(x)
class move_only {
public:
move_only();
move_only(move_only&&);
move_only(move_only const&) = delete;
move_only& operator=(move_only&&);
move_only& operator=(move_only const&) = delete;
void operator()(); │
Вызывается tmp(), где tmp
}; │
конструируется с помощью
auto f5 = std::async(move_only());←┘
std::move(move_only())
По умолчанию реализации предоставлено право решать, запускает ли std::async
новый поток или задача работает синхронно, когда программа ожидает будущего результата. В большинстве случаев такое поведение вас устроит, но можно задать требуемый режим в дополнительном параметре std::async
перед вызываемой функцией. Этот параметр имеет тип std::launch
и может принимать следующие значения: std::launch::deferred
— отложить вызов функции до того момента, когда будет вызвана функция-член wait()
или get()
объекта-будущего; std::launch::async
— запускать функцию в отдельном потоке; std::launch::deferred | std::launch::async
— оставить решение на усмотрение реализации. Последний вариант подразумевается по умолчанию. В случае отложенного вызова функция может вообще никогда не выполниться. Например:
auto f6 = │
Выполнять в
std::async(std::launch::async, Y(), 1.2);←┘
новом потоке
auto f7 =
std::async(
std::launch::deferred, baz, std::ref(x)); ←┐
auto f8 = std::async( ←┐│
Выполнять
std::launch::deferred | std::launch::async,││
при вызове
baz, std::ref(x)); ││
wait() или get()
auto f9 = std::async(baz, std::ref(x)); ←┼
Оставить на
│
усмотрение реализации
f7.wait();←
Вызвать отложенную функцию
Ниже в этой главе и далее в главе 8 мы увидим, что с помощью std::async
легко разбивать алгоритм на параллельно выполняемые задачи. Однако это не единственный способ ассоциировать объект std::future
с задачей; можно также обернуть задачу объектом шаблонного класса std::packaged_task<>
или написать код, который будет явно устанавливать значения с помощью шаблонного класса std::promise<>
. Шаблон std::packaged_task
является абстракцией более высокого уровня, чем std::promise
, поэтому начнем с него.
4.2.2. Ассоциирование задачи с будущим результатом
Шаблон класса std::packaged_task<>
связывает будущий результат с функцией или объектом, допускающим вызов. При вызове объекта std::packaged_task<>
ассоциированная функция или допускающий вызов объект вызывается и делает будущий результат готовым , сохраняя возвращенное значение в виде ассоциированных данных. Этот механизм можно использовать для построение пулов потоков (см. главу 9) и иных схем управления, например, запускать каждую задачу в отдельном потоке или запускать их все последовательно в выделенном фоновом потоке. Если длительную операцию можно разбить на автономные подзадачи, то каждую из них можно обернуть объектом std::packaged_task<>
и передать этот объект планировщику задач или пулу потоков. Таким образом, мы абстрагируем специфику задачи — планировщик имеет дело только с экземплярами std::packaged_task<>
, а не с индивидуальными функциями.
Параметром шаблона класса std::packaged_task<>
является сигнатура функции, например void()
для функции, которая не принимает никаких параметров и не возвращает значения, или int(std::string&, double*)
для функции, которая принимает неконстантную ссылку на std::string
и указатель на double
и возвращает значение типа int
. При конструировании экземпляра std::packaged_task
вы обязаны передать функцию или допускающий вызов объект, который принимает параметры указанных типов и возвращает значение типа, преобразуемого в указанный тип возвращаемого значения. Точного совпадения типов не требуется; можно сконструировать объект std::packaged_task
из функции, которая принимает int
и возвращает float
, потому что между этими типами существуют неявные преобразования.
Тип возвращаемого значения, указанный в сигнатуре функции, определяет тип объекта std::future<>
, возвращаемого функцией-членом get_future()
, а заданный в сигнатуре список аргументов используется для определения сигнатуры оператора вызова в классе упакованной задачи. Например, в листинге ниже приведена часть определения класса std::packaged_task*, int)>
.
Листинг 4.8.Определение частичной специализации std::packaged_task
template<>
class packaged_task*, int)> {
public:
template
explicit packaged_task(Callable&& f);
std::future get_future();
void operator()(std::vector*, int);
};
Таким образом, std::packaged_task
— допускающий вызов объект, и, значит, его можно обернуть объектом std::function
, передать std::thread
в качестве функции потока, передать любой другой функции, которая ожидает допускающий вызов объект, или даже вызвать напрямую. Если std::packaged_task
вызывается как объект-функция, то аргументы, переданные оператору вызова, без изменения передаются обернутой им функции, а возвращенное значение сохраняется в виде асинхронного результата в объекте std::future
, полученном от get_future()
. Следовательно, мы можем обернуть задачу в std::packaged_task
и извлечь будущий результат перед тем, как передавать объект std::packaged_task
в то место, из которого он будет в свое время вызван. Когда результат понадобится, нужно будет подождать готовности будущего результата. В следующем примере показано, как всё это делается на практике.
Во многих каркасах для разработки пользовательского интерфейса требуется, чтобы интерфейс обновлялся только в специально выделенных потоках. Если какому-то другому потоку потребуется обновить интерфейс, то он должен послать сообщение одному из таких выделенных потоков, чтобы тот выполнил операцию. Шаблон std::packaged_task
позволяет решить эту задачу, не заводя специальных сообщений для каждой относящейся к пользовательскому интерфейсу операции.
Интервал:
Закладка: