Блокировка, которую можно перенести из одного потока в другой

Я ищу тип блокировки, при котором поток, удерживающий блокировку, может передать ее другому потоку по своему выбору.

Вот почему я этого хочу:

  • У меня есть класс, похожий на ConcurrentHashMap — специализированная коллекция, разделенная на несколько сегментов.
  • Большинство модификаций требуют блокировки только одного сегмента. Некоторые требуют блокировки двух сегментов (в частности, изменение ключа таким образом, чтобы он перемещался из одного сегмента в другой).
  • Большинство чтений не требуют блокировки — обычно достаточно изменчивого чтения, но иногда при поиске необходимо заблокировать все сегменты сразу, если проверка количества модификаций не удалась.
  • Поиск выполняется в нескольких потоках (через ThreadPoolExecutor)
  • Очень важно, чтобы функция поиска имела единообразное представление обо всех сегментах (например, она не должна пропустить запись при перемещении из одного сегмента в другой).
  • Задача поиска (для одного сегмента) может быть прервана в любой момент.

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

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

Разблокировка и повторная блокировка (на ReentrantLock) небезопасны — другой поток может изменить сегмент до того, как рабочий поток начнет поиск.

Обычный Semaphore может обрабатывать блокировку и разблокировку разными потоками. Тогда возникает вопрос, кто должен освободить семафор: рабочему потоку нужен способ сигнализировать о том, что он взял на себя ответственность за блокировку (поскольку он может выдать исключение до или после этого момента, а основной поток должен знать, следует ли очищать блокировку). up.) Было бы также сложно выполнить модульное тестирование, потому что вы никогда не знаете, произошло ли получение или освобождение семафора в правильном потоке.

Было бы неплохо, если бы блокировку можно было повторно использовать в других методах (где она не передается между потоками).

Я предполагаю, что требуется некоторая комбинация Semaphore и AtomicBoolean или AtomicReference, но быстрый поиск в Google не выявил никаких примеров. Есть ли причина, по которой этот подход не следует использовать?


person finnw    schedule 27.01.2011    source источник
comment
Параллелизм лучше всего работает с простой моделью. Учитывая длину вашего описания и сложность вашей модели, я предлагаю вам попытаться упростить свои требования. ИМХО, у него гораздо больше шансов работать эффективно. Единственный известный мне метод пробуждения определенного потока — это Unsafe.park() и Unsafe.unpark(Thread), но я бы не рекомендовал использовать их напрямую.   -  person Peter Lawrey    schedule 27.01.2011
comment
Если все, что вам нужно, это блокировка, которую можно передать, почему бы не использовать механизм передачи токена, с блокировкой каждого сегмента в качестве токена, который буквально передается? Если время поиска в коллекции достаточно велико, то этот токен может быть даже файлом, который действует как замок. Основной поток может просто передать блокировку рабочему потоку.   -  person Fakrudeen    schedule 27.01.2011
comment
Можно ли смоделировать это без блокировки, имея постоянные структуры с ассоциативными обновлениями, создающими новые структуры, а не изменяя их на месте? Ваше описание относится к решению, а не к основной проблеме, которую вы решаете, поэтому ее трудно предложить.   -  person Jed Wesley-Smith    schedule 28.01.2011


Ответы (1)


Может работать инвертированное использование блокировки чтения/записи. В этой идиоме держатели блокировки чтения выполняют одновременную запись в структуру данных (например, независимые слоты в массиве). Блокировка записи используется для получения эксклюзивного доступа для выполнения согласованного чтения (например, суммирования массива). Это редко используемая идиома, но элегантная в этих странных случаях. Ваша проблема немного сложна для понимания, но это может, по крайней мере, вдохновить вас на конкретное решение. Подозреваю, что при более глубоком понимании задачу можно было бы упростить так, чтобы классическое решение было более подходящим.

person Ben Manes    schedule 27.01.2011
comment
Как я уже сказал, чтение источника ConcurrentHashMap может прояснить ситуацию. Мой класс очень похож, за исключением того, что когда он выполняет эквивалент contains(Object), он ищет каждый сегмент в (потенциально) другом потоке. Я посмотрю, смогу ли я настроить вопрос, чтобы сделать его более ясным. Я думаю, что ваше решение будет работать, хотя. - person finnw; 27.01.2011
comment
Я понимаю технические аспекты, но не поведенческие. Почему ключ перемещается? Почему вам нужно искать (например, почему бы не использовать ConcurrentSkipListMap)? Что за контракт отличает ее от обычной карты? - person Ben Manes; 27.01.2011
comment
нет единого естественного порядка ключей. Эта операция поиска выполняет нечеткое сравнение и должна проверять каждый элемент в сегменте в поисках нескольких ближайших совпадений. Изменение ключа иногда изменяет его хэш-код таким образом, что он принадлежит другому сегменту. Я должен перемещать его атомарно, чтобы гарантировать, что он не останется вне поиска во время его перемещения. - person finnw; 28.01.2011
comment
Хорошо, тогда управление версиями — это классическое решение. Это делается с помощью постоянной структуры данных или реплики, в которой транзакции (поместить/удалить/переместить) воспроизводятся (например, обновления отправляются в очередь записи). Последний может поддерживать одновременный поиск с помощью блокировки чтения/записи, где блокировка записи устанавливается, когда есть ожидающие обновления для воспроизведения. Либо позволит поиску не блокировать другие записи, тогда как обратная блокировка чтения/записи будет блокировать (но может проверять и уступать, чтобы минимизировать это). - person Ben Manes; 28.01.2011