Безопасно ли читать и записывать в массив в разных позициях из нескольких потоков в C с phtreads? - PullRequest
5 голосов
/ 18 июня 2019

Предположим, что есть два потока, A и B. Существует также общий массив: float X[100].

Поток A записывает в массив по одному элементу за раз, каждые 10 шагов обновляет общую переменную index (безопасным способом), которая указывает текущий индекс, и также отправляет сигнал в поток B. Как только поток B получает сигнал, он читает index безопасным способом, а затем приступает к чтению элементов X до положения index.

Безопасно ли это делать? Поток А действительно обновляет массив или просто копирует в кеш?

Ответы [ 3 ]

2 голосов
/ 19 июня 2019

Каждый здравомыслящий способ отправки одним потоком другому потоку обеспечивает гарантию того, что все, что написано потоком перед отправкой сигнала, гарантированно будет видимым потоку после того, как он получит этот сигнал. Так что, если вы отправили сигнал каким-либо способом, который предоставил эту гарантию, что они почти все делают, вы в безопасности.

Обратите внимание, что попытка использовать переменную условия без предиката, защищенного мьютексом, - это , а не разумный способ отправки одним потоком сигнала другому потоку! Помимо прочего, это не гарантирует, что поток, который, по вашему мнению, получил сигнал, действительно получил сигнал. Вы должны убедиться, что поток, выполняющий чтение, действительно получил сигнал, отправленный потоком, выполняющим запись.

2 голосов
/ 19 июня 2019

Безопасно ли это делать?

При условии, что ваша модификация данных представляется безопасной и защищенной критическими секциями, блокировками или чем-то еще, этот вид доступа совершенно безопасен для доступа к оборудованию.

Поток А действительно обновляет массив или просто копию в кеше?

Просто копия в кеше.Большинство кешей в настоящее время с обратной записью и просто записывают данные обратно в память, когда строка извлекается из кеша, если она была изменена.Это в значительной степени улучшает пропускную способность памяти, особенно в многоядерном контексте.

НО все происходит , как если бы память была обновлена.

Для процессоров с общей памятью, как правило, существуют протоколы когерентности кэша (за исключением некоторых процессоров для приложений реального времени),Основная идея этих протоколов заключается в том, что состояние связано с каждой строкой кэша.
Состояние описывает информацию, касающуюся строки в кэше различных процессоров.
Эти состояния указывают, например,если строка присутствует только в текущем кэше или совместно используется несколькими кэшами, синхронно с памятью, недействительна ... См., например, это описание популярного протокола когерентности кэша MESI.

Так что же происходит, когда строка кэша записана и также присутствует в другом процессоре?
Благодаря состоянию кэш знает, что один или несколько других процессоров также имеют копию строки, и он отправит недействительный кодсигнал.Строка будет признана недействительной в других кэшах, и когда они захотят прочитать или записать ее, им придется перезагрузить ее содержимое.На самом деле, эта перезагрузка будет обслуживаться кешем, имеющим действительную копию для ограничения доступа к памяти.

Таким образом, хотя данные записываются только в кеш, поведение аналогично ситуации, в которой данные были бы записаны в память.

НО, несмотря на то, что функционально аппаратное обеспечение будетДля обеспечения правильности передачи необходимо учитывать существование кэша, чтобы избежать снижения производительности.
Предположим, что кэш A обновляет строку, а кэш B читает ее.Всякий раз, когда кэш A записывает, строка в кеше B становится недействительной.И всякий раз, когда кэш B хочет прочитать его, если строка была признана недействительной, он должен извлечь ее из кеша A. Это может привести ко многим переносам строки между кешами и сделать неэффективной систему памяти.

ИтакЧто касается вашего примера, вероятно, 10 не очень хорошая идея, и вам следует использовать информацию о кешах для улучшения обмена между отправителем и получателем.

Например, если вы используете Pentium с 64-байтовыми строками кэша,Вы должны объявить X как

_Alignas(64) float X[100];

Таким образом, начальный адрес X будет кратен 64 и соответствует границам строк кэша.Определитель _Alignas существует с C17, и, включив stdalign.h, вы также можете использовать аналогично alignas(64).До C17 в большинстве компиляторов было несколько расширений для выравнивания размещения.
И, конечно, вы должны указать процессу B для чтения данных только тогда, когда записана полная строка в 64 байта (16 значений с плавающей запятой).

Таким образом, когда поток B получает доступ к данным, строка кэша больше не будет изменяться потоком A, и будет иметь место только одна первоначальная передача между кэшами A и B.Это уменьшение количества передач между кешами может существенно повлиять на производительность в зависимости от вашей программы.

1 голос
/ 19 июня 2019

Если вы используете переменную, которая отслеживает готовность к чтению индекса, переменная защищена мьютексом, и передача сигналов осуществляется через переменную условия pthread, которую поток B ожидает под мьютексом, тогда да.

Если вы используете сигналы POSIX, то, я думаю, вам необходим механизм синхронизации. Запись в атомарную переменную с memory_order_release в потоке A и чтение с memory_order_acquire в потоке B должны гарантировать самый легкий способ, что запись в A, предшествующая записи в атомарную, должна быть видна в B после того, как она прочитала атомарный.

Для лучшей производительности совместное использование массива также должно выполняться таким образом, чтобы общие части массива не пересекали границы строк кэша (иначе производительность может снизиться из-за ложного совместного использования).

...