Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Название:Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-448-1
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ краткое содержание
Книга «Параллельное программирование на С++ в действии» не предполагает предварительных знаний в этой области. Вдумчиво читая ее, вы научитесь писать надежные и элегантные многопоточные программы на С++11. Вы узнаете о том, что такое потоковая модель памяти, и о том, какие средства поддержки многопоточности, в том числе запуска и синхронизации потоков, имеются в стандартной библиотеке. Попутно вы познакомитесь с различными нетривиальными проблемами программирования в условиях параллелизма.
Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Следующими по простоте являются атомарные специализации указателей std::atomic
.
5.2.4. Операции над std::atomic
: арифметика указателей
Атомарная форма указателя на тип T
— std::atomic
— выглядит так же, как атомарная форма bool
( std::atomic
). Интерфейс по существу такой же, только операции применяются к указателям на значения соответствующего типа, а не к значениям типа bool
. Как и в случае std::atomic
, копирующие конструктор и оператор присваивания не определены, но разрешено конструирование и присваивание на основе подходящих указателей. Помимо обязательной функции is_lock_free()
, тип std::atomic
располагает также функциями load()
, store(
), exchange()
, compare_exchange_weak()
и compare_exchange_strong()
с такой же семантикой, как std::atomic
, но принимаются и возвращаются значения типа T*
, а не bool
.
Новыми в типе std::atomic
являются арифметические операции над указателями. Базовые операции предоставляются функциями-членами fetch_add()
и fetch_sub()
, которые прибавляют и вычитают целое число из сохраненного адреса, а также операторы +=
, -=
, ++
и --
(последние в обеих формах — пред и пост), представляющие собой удобные обертки вокруг этих функций. Операторы работают так же, как для встроенных типов: если x
— указатель std::atomic
на первый элемент массива объектов типа Foo
, то после выполнения оператора x+=3
x
будет указывать на четвертый элемент и при этом возвращается простой указатель Foo*
, который также указывает на четвертый элемент. Функции fetch_add()
и fetch_sub()
отличаются от операторов тем, что возвращают старое значение (то есть x.fetch_add(3)
изменит x
, так что оно будет указывать на четвертый элемент, но вернет указатель на первый элемент массива). Эту операцию еще называют обменять-и-прибавить , она относится к категории атомарных операций чтения-модификации-записи, наряду с exchange()
, compare_exchange_weak()
и compare_exchange_strong()
. Как и другие операции такого рода, fetch_add()
возвращает простой указатель T*
, а не ссылку на объект std::atomic
, поэтому вызывающая программа может выполнять действия над прежним значением:
class Foo{};
Foo some_array[5]; │
Прибавить 2 к p
std::atomic p(some_array);│
и вернуть старое
Foo* x = p.fetch_add(2); ←┘
значение
assert(x == some_array);
assert(p.load() == &some_array[2]);
x = (p -= 1); ←┐
Вычесть 1 из p
assert(x == &some_array[1]); │
и вернуть новое
assert(p.load() == &some_array[1]);│
значение
Функциям можно также передать в дополнительном аргументе семантику упорядочения доступа к памяти:
p.fetch_add(3, std::memory_order_release);
Поскольку fetch_add()
и fetch_sub()
— операции чтения-модификации-записи, то они принимают любую семантику упорядочения и могут участвовать в последовательности освобождений . Для операторных форм задать семантику невозможно, поэтому предполагается семантика memory_order_sеq_cst
.
Все прочие атомарные типы по существу одинаковы: это атомарные целочисленные типы с общим интерфейсом, различаются они только ассоциированными встроенными типами. Поэтому я опишу их все сразу.
5.2.5. Операции над стандартными атомарными целочисленными типами
Помимо обычного набора операций ( load()
, store()
, exchange()
, compare_exchange_weak()
и compare_exchange_strong()
), атомарные целочисленные типы такие, как std::atomic
или std::atomic
) обладают целым рядом дополнительных операций: fetch_add()
, fetch_sub()
, fetch_and()
, fetch_or()
, fetch_xor()
, их вариантами в виде составных операторов присваивания ( +=
, -=
, &=
, |=
, ^=
) и операторами пред- и постинкремента и декремента ( ++x
, x++
, --x
, x--
). Это не весь набор составных операторов присваивания, имеющихся у обычного целочисленного типа, но близко к тому — отсутствуют лишь операторы умножения, деления и сдвига. Поскольку атомарные целочисленные значения обычно используются в качестве счетчиков или битовых масок, потеря не слишком велика, а в случае необходимости недостающие операции можно реализовать с помощью вызова функции compare_exchange_weak()
в цикле.
Семантика операций близка к семантике функций fetch_add()
и fetch_sub()
в типе std::atomic
; именованные функции выполняют свои операции атомарно и возвращают старое значение, а составные операторы присваивания возвращают новое значение. Операторы пред- и постинкремента и декремента работают как обычно: ++x
увеличивает значение переменной на единицу и возвращает новое значение, а x++
увеличивает значение переменной на единицу и возвращает старое значение. Как вы теперь уже понимаете, результатом в обоих случаях является значение ассоциированного целочисленного типа.
Мы рассмотрели все простые атомарные типы; остался только основной обобщенный шаблон класса std::atomic<>
без специализации.
5.2.6. Основной шаблон класса std::atomic<>
Наличие основного шаблона позволяет создавать атомарные варианты пользовательских типов, в дополнение к стандартным атомарным типам. Однако в качестве параметра шаблона std::atomic<>
может выступать только тип, удовлетворяющий определенным условиям. Точнее, чтобы тип UDT
мог использоваться в конструкции std::atomic
, в нем должен присутствовать тривиальный оператор присваивания. Это означает, что в типе не должно быть виртуальных функций или виртуальных базовых классов, а оператор присваивания должен генерироваться компилятором. Более того, в каждом базовом классе и нестатическом члене данных также должен быть тривиальный оператор присваивания. Это позволяет компилятору использовать для присваивания функцию memcpy()
или эквивалентную ей, поскольку исполнять написанный пользователем код не требуется.
Наконец, тип должен допускать побитовое сравнение на равенство . Это требование из того же разряда, что требования к присваиванию — должна быть не только возможность колировать объекты с помощью memcpy()
, но и сравнивать их с помощью memcmp()
. Это необходимо для правильной работы операции сравнить-и-обменять.
Чтобы понять, чем вызваны такие ограничения, вспомните рекомендацию из главы 3: не передавать ссылки и указатели на защищенные данные за пределы области видимости в виде аргументов предоставленной пользователем функции. В общем случае компилятор не в состоянии сгенерировать свободный от блокировок код для типа s td::atomic
, поэтому он вынужден применять внутренние блокировки. Если бы пользовательские операторы присваивания и сравнения были разрешены, то пришлось бы передавать ссылку на защищенные данные в пользовательскую функцию, нарушая тем самым приведённую выше рекомендацию. Кроме того, библиотека вправе использовать единую блокировку для всех нуждающихся в ней атомарных операций, поэтому, разрешив вызывать пользовательские функции в момент, когда эта блокировка удерживается, мы могли бы получить взаимоблокировку или надолго задержать другие потоки, если сравнение занимает много времени. Наконец, эти ограничения повышают шансы на то, что компилятор сумеет сгенерировать для std::atomic
код, содержащий истинно атомарные команды (и тем самым обойтись в данной конкретизации вообще без блокировок), поскольку в этой ситуации он вправе рассматривать определенный пользователем тип как неструктурированную последовательность байтов.
Интервал:
Закладка: