Реальный мир Примеры чтения-записи в параллельном программном обеспечении - PullRequest
2 голосов
/ 05 мая 2010

Я ищу реальные примеры необходимости чтения и записи в одно и то же значение в параллельных системах.

По моему мнению, много семафоров или блокировок присутствуют, потому что нет известной альтернативы (реализатору), но знаете ли вы какие-либо шаблоны, где мьютексы кажутся необходимостью?

В некотором смысле, я прошу кандидатов на стандартный набор проблем HARD для параллельного программного обеспечения в реальном мире.

Ответы [ 2 ]

5 голосов
/ 05 мая 2010

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

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

Однако, если то, для чего вы используете данные, может допустить некоторую «разболтанность», есть несколько методов для обмена данными между потоками без использования исключительных блокировок при каждом доступе.

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

Другой пример: если у вас есть объект массива или списка, который в основном читается потоками и обновляется только главным потоком, вы можете реализовать обновления без блокировок, поддерживая две копии списка: одну «живую» «что другие потоки могут читать, а другой -« в автономном режиме », в который вы можете писать в частной жизни вашего собственного потока. Чтобы выполнить обновление, вы копируете содержимое «живого» списка в «автономный» список, выполняете обновление в автономный список, а затем заменяете указатель автономного списка на указатель активного списка с помощью обмена атомарными указателями. Затем вам понадобится какой-то механизм, позволяющий читателям «вытекать» из офлайн-списка. В системе сбора мусора вы можете просто освободить ссылку на автономный список - когда с ним покончит последний потребитель, это будет GC'd. В системе без GC вы можете использовать подсчет ссылок, чтобы отслеживать, сколько читателей все еще используют список. Для этого примера было бы идеально иметь только один поток, обозначенный как средство обновления списка. Если требуется несколько средств обновления, вам потребуется установить блокировку вокруг операции обновления, но только для сериализации средств обновления - без блокировки и без влияния на производительность читателей списка.

Все известные мне методы совместного использования ресурсов без блокировки требуют использования атомарных перестановок (или InterlockedExchange). Обычно это приводит к конкретной инструкции в процессоре и / или блокировке аппаратной шины (префикс блокировки для кода операции чтения или записи в ассемблере x86) в течение очень короткого периода времени. В многопроцессорных системах атомные перестановки могут вызвать аннулирование кэша на других процессорах (это имело место в Pentium II с двумя процессорами), но я не думаю, что это является большой проблемой для современных многоядерных чипов. Даже с этими предупреждениями о производительности без блокировок работает намного быстрее, чем с полным событием объекта ядра. Просто вызов функции API ядра занимает несколько сотен тактов (для переключения в режим ядра).

Примеры реальных сценариев:

  1. рабочие процессы производителя / потребителя. Веб-служба получает запросы http для данных, помещает запрос во внутреннюю очередь, рабочий поток извлекает рабочий элемент из очереди и выполняет работу. Очередь предназначена для чтения / записи и должна быть поточно-ориентированной.
  2. Данные распределяются между потоками при смене владельца. Поток 1 выделяет объект, бросает его потоку 2 для обработки и никогда больше не хочет его видеть. Поток 2 отвечает за утилизацию объекта. Система управления памятью (malloc / free) должна быть поточно-ориентированной.
  3. Файловая система. Это почти всегда служба ОС и уже полностью поточно-ориентированная, но ее стоит включить в список.
  4. Подсчет ссылок. Освобождает ресурс, когда количество ссылок падает до нуля. Операции увеличения / уменьшения / проверки должны быть поточно-ориентированными. Обычно они могут быть реализованы с использованием атомарных примитивов вместо полноценных блокировок Kernel mutex.
2 голосов
/ 05 мая 2010

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

Например, я часто делаю симуляции, когда происходит какая-то операция агрегации. Как правило, существуют способы предотвратить блокировку во время самой фазы моделирования (например, использование данных локального состояния потока и т. Д.), Но фактическая часть агрегации обычно требует некоторой формы блокировки в конце.

К счастью, это становится блокировкой для потока, а не для единицы работы. В моем случае это важно, поскольку я обычно выполняю операции на сотнях тысяч или миллионах единиц работы, но большую часть времени это происходит в системах с 4-16 PE, что означает, что я обычно ограничиваюсь аналогичное количество единиц исполнения. Используя этот тип механизма, вы все еще блокируете, но блокируете между десятками элементов, а не миллионами.

...