Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Название:Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-448-1
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ краткое содержание
Книга «Параллельное программирование на С++ в действии» не предполагает предварительных знаний в этой области. Вдумчиво читая ее, вы научитесь писать надежные и элегантные многопоточные программы на С++11. Вы узнаете о том, что такое потоковая модель памяти, и о том, какие средства поддержки многопоточности, в том числе запуска и синхронизации потоков, имеются в стандартной библиотеке. Попутно вы познакомитесь с различными нетривиальными проблемами программирования в условиях параллелизма.
Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
int main() {
std::function offset_42 = make_offseter(42);
std::function offset_123 = make_offseter(123);
std::cout <<
offset_42(12) << "," << offset_123(12) << std::endl;
std::cout <<
offset_42(12) << "," << offset_123(12) << std::endl;
}
два раза выведет числа 54, 135
, потому что функция, возвращенная после первого обращения к make_offseter
, всегда добавляет 42 к переданному ей аргументу Напротив, функция, возвращенная после второго обращения к make_offseter
, добавляет к своему аргументу 123. Это самый безопасный вид захвата локальных переменных — все значения копируются, поэтому лямбда-функцию можно вернуть и вызывать вне контекста функции, в которой она была создана. Но это не единственно возможное решение, можно захватывать локальные переменные и по ссылке. В таком случае попытка вызвать лямбда-функцию после того, как переменные, на которые указывают ссылки, были уничтожены в результате выхода из области видимости объемлющей их функции или блока, приведёт к неопределённому поведению, точно так же, как обращение к уничтоженной переменной в любом другом случае.
Лямбда-функция, захватывающая все локальные переменные по ссылке, начинается интродуктором [&]
:
int main() {
int offset = 42; ←
(1)
std::function offset_a =
[&](int j){return offset + j;};←
(2)
offset = 123; ←
(3)
std::function offset_b =
[&](int j){return offset + j;};←
(4)
std::cout <<
offset_a(12) << "," << offset_b(12) << std::endl; ←
(5)
offset = 99; ←
(6)
std::cout <<
offset_a(12) << "," << offset_b(12) << std::endl; ←
(7)
}
Если функция make_offseter
из предыдущего примера захватывала копию смещения offset
, то функция offset_a
в этом примере, начинающаяся интродуктором [&]
, захватывает offset
по ссылке (2). Неважно, что начальное значение offset
было равно 42 (1); результат вызова offset_a(12)
зависит от текущего значения offset
. Значение offset
было изменено на 123 (3)перед порождением второй (идентичной) лямбда-функции offset_b
(4), но эта вторая функция снова производит захват по ссылке, поэтому результат, как и прежде, зависит от текущего значения offset
.
Теперь при печати первой строки (5), offset
всё еще равно 123, поэтому печатаются числа 133, 135
. Однако к моменту печати второй строки (7) offset
стало равно 99 (6), поэтому печатается 111, 111
. И offset_a
, и offset_b
прибавляют текущее значение offset
(99) к переданному аргументу (12).
Но ведь это С++, поэтому вам не обязательно выбирать между всем или ничем; вполне можно захватывать одни переменные по значению, а другие по ссылке. Более того, можно даже указывать, какие именно переменные захватить. Нужно лишь изменить лямбда-интродуктор. Если требуется скопировать все видимые переменные, кроме одной-двух, то воспользуйтесь интродуктором [=]
, но после знака равенства перечислите переменные, захватываемые по ссылке, предпослав им знаки амперсанда. В следующем примере печатается 1239
, потому что переменная i
копируется в лямбда-функцию, a j
и k
захватываются по ссылке:
int main() {
int i=1234, j=5678, k=9;
std::function f=[=,&j,&k] {return i+j+k;};
i = 1;
j = 2;
k = 3;
std::cout << f() << std::endl;
}
Можно поступить и наоборот — по умолчанию захватывать по ссылке, но некоторое подмножество переменных копировать. В таком случае воспользуйтесь интродуктором [&]
, а после знака амперсанда перечислите переменные, захватываемые по значению. В следующем примере печатается 5688
, потому что i
захватывается по ссылке, a j
и k
копируются:
int main() {
int i=1234, j=5678, k= 9;
std::function f=[&,j,k] {return i+j+k;};
i = 1;
j = 2;
k = 3;
std::cout << f() << std::endl;
}
Если требуется захватить только именованные переменные, то можно опустить знак =
или &
и просто перечислить захватываемые переменные, предпослав знак амперсанда тем, что должны захватываться по ссылке, а не по значению. В следующем примере печатается 5682
, потому что i
и k
захвачены по ссылке, a j
скопирована
int main() {
int i=1234, j=5678, k=9;
std::function f=[&i, j, &k] {return i+j+k;};
i =
1;
j = 2;
k = 3;
std::cout << f() << std::endl;
}
Последний способ заодно гарантирует, что захвачены только необходимые переменные, потому что ссылка на локальную переменную, отсутствующую в списке захвата, приведёт к ошибке компиляции. Выбирая этот вариант, нужно соблюдать осторожность при доступе к членам класса, если лямбда-функция погружена в функцию-член класса. Члены класса нельзя захватывать непосредственно; если к ним необходим доступ из лямбда-функции, то необходимо захватить указатель this
, включив его в список захвата. В следующем примере лямбда-функция захватывает this
для доступа к члену класса some_data
:
struct X {
int some_data;
void foo(std::vector& vec) {
std::for_each(vec.begin(), vec.end(),
[this](int& i){ i += some_data; });
}
};
В контексте параллелизма лямбда-функции особенно полезны для задания предикатов функции std::condition_variable::wait()
(см. раздел 4.1.1) и в сочетании с std::packaged_task<>
(раздел 4.2.1) или пулами потоков для упаковки небольших задач. Их можно также передавать конструктору std::thread
в качестве функций потока (раздел 2.1.1) и в качестве исполняемой функции в таких параллельных алгоритмах, как parallel_for_each()
(раздел 8.5.1).
А.6. Шаблоны с переменным числом параметров
Функции с переменным числом параметров, например printf
, используются уже давно, а теперь появились и шаблоны с переменным числом параметров (variadic templates). Такие шаблоны применяются во многих местах библиотеки С++ Thread Library. Например, конструктор std::thread
для запуска потока (раздел 2.1.1) — это шаблон функции с переменным числом параметров, a std::packaged_task<>
(раздел 4.2.2) — шаблон класса с переменным числом параметров. С точки зрения пользователя, достаточно знать, что шаблон принимает неограниченное количество параметров, но если вы хотите написать такой шаблон или просто любопытствуете, как это работает, то детали будут небезынтересны.
При объявлении шаблонов с переменным числом параметров, по аналогии с обычными функциями, употребляется многоточие ( ...
) в списке параметров шаблона:
template
class my_template {};
Переменное число параметров допустимо и в частичных специализациях шаблона, даже если основной шаблон содержит фиксированное число параметров. Например, основной шаблон std::packaged_task<>
(раздел 4.2.1) — это простой шаблон с единственным параметром:
Интервал:
Закладка: