Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Название:Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-448-1
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ краткое содержание
Книга «Параллельное программирование на С++ в действии» не предполагает предварительных знаний в этой области. Вдумчиво читая ее, вы научитесь писать надежные и элегантные многопоточные программы на С++11. Вы узнаете о том, что такое потоковая модель памяти, и о том, какие средства поддержки многопоточности, в том числе запуска и синхронизации потоков, имеются в стандартной библиотеке. Попутно вы познакомитесь с различными нетривиальными проблемами программирования в условиях параллелизма.
Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Следовательно, при тестировании производительности многопоточной программы лучше замерять результаты в максимально широком спектре конфигураций, чтобы можно было составить целостное представление о масштабируемости. Как минимум, следует прогнать тесты на однопроцессорной машине и на машине с максимальным числом процессорных ядер, которое вы можете себе позволить.
10.3. Резюме
В этой главе мы рассмотрели различные виды ошибок, связанных с параллелизмом, — от взаимоблокировок и активных блокировок до гонок за данными и других проблематичных состояний гонки. Были описаны различные методы поиска ошибок. Я сформулировал вопросы, над которыми следует поразмыслить, дал рекомендации по написанию тестопригодного кода и рассказал о том, как структурировать тесты для параллельных программ. И, наконец, мы затронули вопрос о некоторых служебных компонентах, которые могут оказать помощь в процессе тестирования.
Приложение А.
Краткий справочник по некоторым конструкциям языка С++
Новый стандарт С++ отнюдь не исчерпывается поддержкой параллелизма; в нем появилось немало других языковых средств и новых библиотек. В этом приложении я вкратце расскажу о тех новых возможностях, которые используются в библиотеке многопоточности и встречаются в этой книге. За исключением модификатора thread_local
(рассматриваемого в разделе А.8), все они не имеют прямого отношения к параллелизму, однако важны и (или) полезны для написания многопоточного кода. Я ограничился лишь теми конструкциями, которые либо необходимы (например, ссылки на r -значения), либо делают код проще и яснее. Поначалу разобраться в программе, где применяются эти конструкции, будет трудно, но, познакомившись с ними поближе, вы согласитесь, что, вообще говоря, включающий их код проще, а не сложнее для понимания. По мере распространения С++11 описываемые средства будут встречаться в программах все чаще.
А теперь, без дальнейших предисловий, начнем с изучения ссылок на r-значения — средства, которое широко используется в библиотеке Thread Library для передачи владения (потоками, блокировками и вообще всем на свете) от одного объекта другому.
А.1. Ссылки на r -значения
Всякий, кто программировал на С++, знаком со ссылками; в С++ ссылки служат для создания альтернативного имени существующего объекта. Любой доступ к объекту по ссылке, в том числе для модификации, приводит к манипуляциям с исходным объектом. Например:
int var = 42; │
Создаем ссылку
int& ref = var;←┘
на var
ref = 99; │
В результате присваивания ссылке
assert (var == 99);←┘
изменен оригинал
Ссылки, к которым мы все давно привыкли, являются ссылками на l-значения. Термин l-значение появился еще в языке С и обозначает любую конструкцию, которая может находиться в левой части выражения присваивания, — именованные объекты, объекты, созданные в стеке или в куче, или члены других объектов, то есть сущности, расположенные по определенному адресу в памяти. Термин r-значение также происходит из С и обозначает конструкции, которые могут находиться только в правой части выражения присваивания, — например, литералы и временные объекты. Ссылки на l -значения можно связать только с l -значениями, но не с r -значениями. Так, невозможно написать
int& i = 42;
потому что 42 — это r -значение. Впрочем, это не совсем верно; всегда разрешалось связывать r -значение с константной ссылкой на l -значение:
int const& i = 42;
Однако в стандарте это исключение сделано сознательно задолго до появления ссылок на r -значения, и смысл его в том, чтобы разрешить передавать временные объекты функциям, принимающим ссылки. Благодаря этому механизму становятся возможны неявные преобразования, например, можно написать:
void print(std::string const& s);
print("hello");
Как бы то ни было, в стандарте C++11 официально введены ссылки на r-значения , которые связываются только с r -значениями, но не с l -значениями, и объявляются с помощью двух знаков амперсанда:
int&& i = 42;
int j = 42;
int&& k = j;
Таким образом, функцию можно перегрузить в зависимости от того, являются параметры l -значениями или r -значениями, — один вариант будет принимать ссылку на l -значение, другой — на r -значение. Эта возможность — краеугольный камень семантики перемещения .
A.1.1. Семантика перемещения
r -значения — это обычно временные объекты, поэтому их можно спокойно модифицировать; если известно, что параметр функции — r-значение, то его можно использовать как временную память, то есть «позаимствовать» его содержимое без ущерба для корректности программы. Это означает, что вместо копирования параметра, являющегося r -значением, мы можем просто переместить его содержимое. В случае больших динамических структур это позволяет сэкономить на выделении памяти и оставляет простор для оптимизации.
Рассмотрим функцию, которая принимает в качестве параметра std::vector
и хочет иметь его внутреннюю копию для модификации, так чтобы не затрагивать оригинал. Раньше мы для этого должны были принимать параметр как const
-ссылку на l -значение и делать внутреннюю копию:
void process_copy(std::vector const& vec_) {
std::vector vec(vec_);
vec.push_back(42);
}
При этом функция может принимать как l -значения, так и r -значения, но копирование производится всегда. Однако, если добавить перегруженный вариант, который принимает ссылку на r -значение, то в этом случае можно будет избежать копирования, поскольку нам точно известно, что оригинал разрешается модифицировать:
void process_copy(std::vector&& vec) {
vec.push_back(42);
}
Если функция является конструктором класса, то можно умыкнуть содержимое r -значения и воспользоваться им для создания нового экземпляра. Рассмотрим класс, показанный в листинге ниже. В конструкторе по умолчанию он выделяет большой блок памяти, а в деструкторе освобождает его.
Листинг А.1.Класс с перемещающим конструктором
class X {
private:
int* data;
public:
X() : data(new int[1000000]) {}
~X() {
delete [] data;
}
X(const X& other) : ←
(1)
data(new int[1000000]) {
std::copy(other.data, other.data + 1000000, data);
}
X(X&& other) : ←
(2)
data(other.data) {
other.data = nullptr;
}
};
Копирующий конструктор (1)определяется как обычно: выделяем новый блок памяти и копируем в него данные. Но теперь у нас есть еще один конструктор, который принимает ссылку на r -значение (2). Это перемещающий конструктор . В данном случае мы копируем только указатель на данные, а в объекте other
остается нулевой указатель. Таким образом, мы обошлись без выделения огромного блока памяти и сэкономили время на копировании данных из r -значения.
Интервал:
Закладка: