Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Название:Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-448-1
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ краткое содержание
Книга «Параллельное программирование на С++ в действии» не предполагает предварительных знаний в этой области. Вдумчиво читая ее, вы научитесь писать надежные и элегантные многопоточные программы на С++11. Вы узнаете о том, что такое потоковая модель памяти, и о том, какие средства поддержки многопоточности, в том числе запуска и синхронизации потоков, имеются в стандартной библиотеке. Попутно вы познакомитесь с различными нетривиальными проблемами программирования в условиях параллелизма.
Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Но за простоту понимания приходится платить. На машине со слабым упорядочением и большим количеством процессоров может наблюдаться заметное снижение производительности, потому что для поддержания согласованной последовательности операций, возможно, придётся часто выполнять дорогостоящие операции синхронизации процессоров. Вместе с тем следует отметить, что некоторые архитектуры процессоров (в частности, такие распространенные, как x86 и x86-64) обеспечивают последовательную согласованность с относительно низкими издержками, так что если вас волнует влияние последовательно согласованного упорядочения на производительность, ознакомьтесь с документацией но конкретному процессору.
В следующем листинге последовательная согласованность демонстрируется на примере. Операции загрузки и сохранения переменных x
и y
явно помечены признаком memory_order_seq_cst
, хотя его можно было бы и опустить, так как он подразумевается по умолчанию.
Листинг 5.4.Из последовательной согласованности вытекает полная упорядоченность
#include
#include
#include
std::atomic x, y;
std::atomic z;
void write_x() {
x.store(true, std::memory_order_seq_cst); ←
(1)
}
void write_y() {
y.store(true, std::memory_order_seq_cst); ←
(2)
}
void read_x_then_y() {
while (!x.load(std::memory_order_seq_cst));←
(3)
if (y.load(std::memory_order_seq_cst))
++z;
}
void read_y_then_x() {
while (!y.load(std::memory_order_seq_cst));←
(4)
if (x.load(std::memory_order_seq_cst))
++z;
}
int main() {
x = false;
y = false;
z = 0;
std::thread a(write_x);
std::thread b(write_y);
std::thread с(read_x_then_y);
std::thread d(read_y_then_x);
a.join();
b.join();
c.join();
d.join();
assert(z.load() != 0); ←
(5)
}
Утверждение assert
(5)не может сработать, потому что первым должно произойти сохранение x
(1)или сохранение y
(2), пусть даже точно не сказано, какое именно. Если загрузка y
в функции read_x_then_y
(3)возвращает false
, то сохранение x
должно было произойти раньше сохранения y
, и в таком случае загрузка x
в read_y_then_x
(4)должна вернуть true
, потому что наличие цикла while
гарантирует, что в этой точке у
равно true
. Поскольку семантика memory_order_seq_cst
требует полного упорядочения всех операций, помеченных признаком memory_order_seq_cst
, то существует подразумеваемое отношение порядка между операцией загрузки y
, которая возвращает false
(3), и операцией сохранения y
(1). Чтобы имело место единственное полное упорядочение в случае, когда некоторый поток сначала видит x==true
, затем y==false
, необходимо, чтобы при таком упорядочении сохранение x
происходило раньше сохранения y
.
Разумеется, поскольку всё симметрично, могло бы произойти и ровно наоборот: загрузка x
(4)возвращает false
, и тогда загрузка y
(3)обязана вернуть true
. В обоих случаях z
равно 1. Может быть и так, что обе операции вернут true
, и тогда z
будет равно 2. Но ни в каком случае z
не может оказаться равным нулю.
Операции и отношения происходит-раньше для случая, когда read_x_then_y
видит, что x
равно true
, а y
равно false
, изображены на рис. 5.3. Пунктирная линия от операции загрузки y
в read_x_then_y
к операции сохранения y
в write_y
показывает наличие неявного отношения порядка, необходимого для поддержания последовательной согласованности: загрузка должна произойти раньше сохранения в глобальном порядке операций, помеченных признаком memory_order_seq_cst
, — только тогда получится показанный на рисунке результат.

Рис. 5.3.Последовательная согласованность и отношения происходит-раньше
Последовательная согласованность — самое простое и интуитивно понятное упорядочение, но оно же является и самым накладным из- за необходимости глобальной синхронизации между всеми потоками. В многопроцессорной системе это потребовало бы многочисленных и затратных по времени взаимодействий между процессорами. Чтобы избежать затрат на синхронизацию, необходимо выйти за пределы мира последовательной согласованности и рассмотреть другие модели упорядочения доступа к памяти.
За пределами уютного последовательно согласованного мирка нас встречает более сложная реальность. И, пожалуй, самое трудное — смириться с тем фактом, что единого глобального порядка событий больше не существует . Это означает, что разные потоки могут по-разному видеть одни и те же операции, и с любой умозрительной моделью, предполагающей, что операции, выполняемые в разных потоках, строго перемежаются, следует распрощаться. Вы должны учитывать не только то, что события могут происходить по-настоящему одновременно, но и то, что потоки не обязаны согласовывать порядок событий между собой . Чтобы написать (или хотя бы понять) код, в котором используется упорядочение, отличное от memory_order_seq_cst
, абсолютно необходимо уложить этот факт в мозгу. Мало того что компилятор вправе изменять порядок команд. Даже если потоки исполняют один и тот же код, они могут видеть события в разном порядке, потому что в отсутствие явных ограничений на упорядочение кэши различных процессоров и внутренние буферы могут содержать различные значения для одной и той же ячейки памяти. Это настолько важно, что я еще раз повторю: потоки не обязаны согласовывать порядок событий между собой .
Вы должны отбросить мысленные модели, основанные не только на идее чередования операций, но и на представлении о том, что компилятор или процессор изменяет порядок команд. В отсутствие иных ограничений на упорядочение, единственное требование заключается в том, что все потоки согласны относительно порядка модификации каждой отдельной переменной. Операции над различными переменными могут быть видны разным потокам в разном порядке при условии, что видимые значения согласуются с наложенными дополнительными ограничениями на упорядочение.
Проще всего это продемонстрировать, перейдя от последовательной согласованности к ее полной противоположности — упорядочению memory_order_relaxed
для всех операций. Освоив этот случай, мы сможем вернуться к упорядочению захват-освобождение, которое позволяет избирательно вводить некоторые отношения порядка между операциями. Это хоть как-то поможет собрать разлетевшиеся мозги в кучку.
Интервал:
Закладка: