Проблема проектирования: Потоковая безопасность std :: map - PullRequest
1 голос
/ 16 августа 2011

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

Нужно ли помещать операцию поиска в критическую секцию? Повлияет ли операция поиска на операции вставки / удаления? Есть ли лучшая реализация, чем использование std :: map, которая может позаботиться обо всем?

Ответы [ 5 ]

5 голосов
/ 16 августа 2011

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

Я бы настоятельно рекомендовал использовать уже написанные потокобезопасные контейнеры. Например, Intel TBB содержит concurrent_hash_map.

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

Устройство чтения / записи

Вместо обычного Mutex вы можете использовать Mutex для чтения / записи. Это означает распараллеливание операций чтения, в то время как операции записи остаются строго последовательными.

Собственное дерево

Вы также можете создать свое собственное красно-черное дерево или дерево AVL. Расширяя древовидную структуру мьютексом Reader / Writer для каждого узла. Это позволяет блокировать часть дерева, а не всю структуру, даже при перебалансировке. например, вставки с достаточно далеко расположенными клавишами могут быть параллельными.

Пропуск списков

Связанные списки гораздо более поддаются одновременным манипуляциям, поскольку вы можете легко изолировать модифицированную зону.

A Список пропусков основывается на этой силе, но дополняет структуру, чтобы обеспечить доступ O (log N) по ключу.

Типичный способ обхода списка - использование идиомы hand hand *1041*, то есть захват мьютекса следующего узла перед освобождением одного из текущего узла. Пропускные списки добавляют второе измерение, поскольку вы можете погружаться между двумя узлами, освобождая их оба (и позволяя другим ходячим идти впереди вас).

Реализации намного проще, чем для деревьев двоичного поиска.

Стойкие

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

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

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

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

Подсчет ссылок (встроенный) - ваш друг для этих узлов.

Примечание: копии дерева очень дешевы:)


Я не знаю ни одной реализации в C ++ ни параллельного списка пропусков, ни параллельного полупостоянного дерева двоичного поиска.

1 голос
/ 16 августа 2011

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

Такая реализация будет работать с большинством реализаций STL, но не будет соответствовать стандартам. std::map обычно реализуется с использованием красно-черного дерева , которое не изменяется при чтении элементов. Если бы карта была реализована с использованием дерева сплайнов , дерево изменилось бы во время поиска, и только один поток мог читать одновременно.

Для большинства целей я бы рекомендовал использовать два замка.

1 голос
/ 16 августа 2011

Да - вам нужно поместить вставку, удаление и поиск в критическом разделе. Существуют методы для одновременного включения нескольких находок.

1 голос
/ 16 августа 2011

Да, если вставка или удаление приводят к перебалансировке, я считаю, что на find это тоже может повлиять.

0 голосов
/ 16 августа 2011

Из того, что я вижу, здесь был дан ответ на аналогичный вопрос, и ответ включает в себя также объяснение этого вопроса, а также ссылку, объясняющую безопасность потока более подробно.

Потоковая безопасность std :: map для операций только для чтения

...