Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Название:Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-448-1
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ краткое содержание
Книга «Параллельное программирование на С++ в действии» не предполагает предварительных знаний в этой области. Вдумчиво читая ее, вы научитесь писать надежные и элегантные многопоточные программы на С++11. Вы узнаете о том, что такое потоковая модель памяти, и о том, какие средства поддержки многопоточности, в том числе запуска и синхронизации потоков, имеются в стандартной библиотеке. Попутно вы познакомитесь с различными нетривиальными проблемами программирования в условиях параллелизма.
Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
std::atomic x, y;
std::atomic z;
void write_x() {
x.store(true, std::memory_order_release);
}
void write_y() {
y.store(true, std::memory_order_release);
}
void read_x_then_y() {
while (!x.load(std::memory_order_acquire));
if (y.load(std::memory_order_acquire)) ←
(1)
++z;
}
void read_y_then_x() {
while (!y.load(std::memory_order_acquire));
if (x.load(std::memory_order_acquire)) ←
(2)
++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); ←
(3)
}
В данном случае утверждение (3) может сработать (как и в случае ослабленного упорядочения), потому что обе операции загрузки — x
(2)и y
(1)могут прочитать значение false
. Запись в переменные x
и y
производится из разных потоков, но упорядоченность между освобождением и захватом в одном потоке никак не отражается на операциях в других потоках.
На рис. 5.6 показаны отношения происходит-раньше, имеющие место в программе из листинга 5.7, а также возможный исход, когда два потока-читателя имеют разное представление о мире. Это возможно, потому что, как уже было сказано, не существует отношения происходит-раньше, которое вводило бы упорядочение.

Рис. 5.6.Захват-освобождение и отношения происходит-раньше
Чтобы осознать преимущества упорядочения захват-освобождение, нужно рассмотреть две операции сохранения в одном потоке, как в листинге 5.5. Если при сохранении y
задать семантику memory_order_release
, а при загрузке y
— семантику memory_order_acquire
, как в листинге ниже, то операции над x
станут упорядоченными.
Листинг 5.8.Операции с семантикой захвата-освобождения могут упорядочить ослабленные операции
#include
#include
#include
std::atomic x, y;
std::atomic z;
void write_x_then_y() {
x.store(true,std::memory_order_relaxed); ←
(1)
y.store(true,std::memory_order_release); ←
(2)
}
void read_y_then_x() {
while (!y.load(std::memory_order_acquire));←
(3)
if (x.load(std::memory_order_relaxed)) ←
(4)
++z;
}
int main() {
x = false;
y = false;
z = 0;
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
assert(z.load() != 0); ←
(5)
}
В конечном итоге операция загрузки y
(3)увидит значение true
, записанное операцией сохранения (2). Поскольку сохранение производится в режиме memory_order_release
, а загрузка — в режиме memory_order_acquire
, то сохранение синхронизируется-с загрузкой. Сохранение x
(1)происходит-раньше сохранения y
(2), потому что обе операции выполняются в одном потоке. Поскольку сохранение y
синхронизируется-с загрузкой y
, то сохранение x
также происходит-раньше загрузки y
, и, следовательно, происходит-раньше загрузки x
(4). Таким образом, операция загрузки x
должна прочитать true
, и, значит, утверждение (5) не может сработать. Если бы загрузка y
не повторялась в цикле while
, то высказанное утверждение могло бы оказаться неверным; операция загрузки y
могла бы прочитать false
, и тогда не было бы никаких ограничений на значение, прочитанное из x
. Для обеспечения синхронизации операции захвата и освобождения должны употребляться парами. Значение, сохраненное операций восстановления, должно быть видно операции захвата, иначе ни та, ни другая не возымеют эффекта. Если бы сохранение в предложении (2)или загрузка в предложении (3)выполнялись в ослабленной операции, то обращения к x
не были бы упорядочены, и, значит, нельзя было бы гарантировать, что операция загрузки в предложении (4)прочитает значение true
, поэтому утверждение assert
могло бы сработать.
К упорядочению захват-освобождение можно применить метафору человека с блокнотом в боксе, если ее немного дополнить. Во-первых, допустим, что каждое сохранение является частью некоторого пакета обновлений, поэтому, обращаясь к человеку с просьбой записать число, вы заодно сообщается ему идентификатор пакета, например: «Запиши 99 как часть пакета 423». Если речь идет о последнем сохранении в пакете, то мы сообщаем об этом: «Запиши 147, отметив, что это последнее сохранение в пакете 423». Человек в боксе честно записывает эту информацию вместе с указанным вами значением. Так моделируется операция сохранения с освобождением. Когда вы в следующий раз попросите записать значение, помер пакета нужно будет увеличить: «Запиши 41 как часть пакета 424».
Теперь, когда вы просите сообщить значение, у вас есть выбор: узнать только значение (это аналог ослабленной загрузки) или значение и сведения о том, является ли оно последним в пакете (это аналог загрузки с захватом). Если информация о пакете запрашивается, по значение не последнее в пакете, то человек ответит: «Число равно 987, и это 'обычное' значение»; если же значение последнее, то ответ прозвучит так: «Число 987, последнее в пакете 956 от Анны». Тут-то и проявляется семантика захвата-освобождения: если, запрашивая значение, вы сообщите человеку номера всех пакетов, о которых знаете, то он найдёт в своем списке последнее значение из всех известных вам пакетов и назовёт либо его, либо какое-нибудь следующее за ним в списке.
Как эта метафора моделирует семантику захвата-освобождения? Взгляните на наш пример — и поймете. В самом начале поток а
вызывает функцию write_x_then_y
и говорит человеку в боксе x
: «Запиши true
, как часть пакета 1 от потока а
». Затем поток а
говорит человеку в боксе y
: «Запиши true
, как последнюю операцию записи в пакете 1 от потока а
». Тем временем поток b
выполняет функцию read_y_then_x
. Он раз за разом просит человека в боксе y
сообщить значение вместе с информацией о пакете, пока не услышит в ответ « true
». Возможно, спросить придется много раз, но в конце концов человек обязательно ответит « true
». Однако человек в боксе y
говорит не просто « true
», а еще добавляет: «Это последняя операция записи в пакете 1 от потока а
».
Далее поток b
просит человека в боксе x
назвать значение, но на это раз говорит: «Сообщи мне значение и, кстати, я знаю о пакете 1 от потока а
». Человек в боксе x ищет в своем списке последнее упоминание о пакете 1 от потока а
. Он находит единственное значение true
, которое стоит последним в списке, поэтому он обязан сообщить именно это значение, иначе нарушит правила игры.
Интервал:
Закладка: