Тысячи читателей / писателей блокируются в одном процессе - PullRequest
6 голосов
/ 06 августа 2011

В настоящее время я разрабатываю кроссплатформенное серверное приложение C ++ (Linux / Windows) с масштабным шаблоном синхронизации.Я внутренне использую boost :: thread в качестве абстракции специфичных для ОС потоков.Моя задача - защитить массив данных, каждый элемент массива защищен независимой блокировкой чтения / записи .

Мой массив содержит 4096 элементов .Учитывая решение проблемы «писатель-приоритет читателей-писателей», представленной в « Маленькая книга семафоров » (стр. 85), моему приложению потребовалось бы 5 семафоров на элемент массива.Это дает в общей сложности около 20000 семафоров (или, что эквивалентно, 20000 мьютексов + 20000 переменных условий).

Дополнительная специфика моего приложения заключается в том, что в данное время большинство семафоров неактивны (обычно их около 32).«клиентские» потоки, ожидающие / сигнализирующие о тысячах семафоров).Обратите внимание, что поскольку весь сервер выполняется в одном процессе, я использую облегченные семафоры на основе потоков (, а не межпроцессные семафоры).

Мой вопрос состоит из двух частей:

  1. Рекомендуется ли создавать в общей сложности 20000 семафоров в Linux и в Windows для одного процесса?Ну, конечно, я предполагаю, что это не тот случай ...

  2. Если эта практика не рекомендуется, какую технику я могу использовать, чтобы уменьшить количество фактических семафоров, например, чтобы создать набор из N "эмулируемых семафоров" поверх 1 фактического семафора ?Я полагаю, что это было бы интересным решением, потому что большинство моих семафоров в данный момент неактивны.

Заранее спасибо!

Резюме ответов на данный момент

  1. Использование тысяч семафоров не рекомендуется , особенно с точки зрения кроссплатформенности.И поэтому, даже если они не являются межпроцессными семафорами (они по-прежнему используют дескрипторы под Windows).
  2. Прямой способ решения моей проблемы - разделить мой массив, например, на 64 подмассива по 16 элементов и на свяжите каждый из этих подмассивов с одной блокировкой чтения / записи .К сожалению, это вызывает много разногласий (1 писатель блокирует чтение для 15 элементов).
  3. Копаясь в исходном коде Boost, я обнаружил, что:

    • Реализация«boost :: mutex» не переносит объекты CRITICAL_SECTION под Windows (кроме CreateEvent и ReadWriteBarrier),
    • «boost :: shared_mutex» использует CreateSemaphore под Windows (которые являются тяжеловесными, межпроцессными объектами) и
    • "boost :: shared_mutex" не переносит "pthread_rwlock_t" под Linux.

    Причины этого не кажутся мне понятными.В частности, использование межпроцессных объектов для «boost :: shared_mutex» под Windows кажется мне неоптимальным.

Резюме открытых вопросов на данный момент

  1. Как я могу создать набор из N "эмулируемых семафоров" на вершине 1 фактического семафора, поддерживая как можно меньшую конкуренцию между эмулируемыми семафорами?
  2. Как сделать "boost :: mutex" и "boost :: shared_mutex "сравнить со своими родными аналогами (CRITICAL_SECTION и pthread_rwlock_t)?

Ответы [ 3 ]

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

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

Во-первых, нет абсолютно никаких проблем с созданием семафоров по 20 тыс. Для одного процесса. Это довольно легкий объект ядра. Даже "межпроцессные" семафоры.

Однако я вижу еще одну проблему с вашим дизайном. Вы должны знать, что каждая операция, которую вы выполняете с объектом ядра (таким как семафор / мьютекс), включает в себя тяжелую транзакцию в режиме ядра (системный вызов a.k.a.). Каждый такой вызов может стоить вам около 2 тыс. Циклов ЦП, даже если коллизий нет вообще.

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

Напротив, для синхронизации потоков можно использовать взаимосвязанные операции. Они стоят намного дешевле (как правило, десятки циклов ЦП).

Существует также объект с именем критическое сечение . Это своего рода гибрид взаимосвязанного операнда и объекта ядра (который используется в случае реального столкновения). Вы должны проверить, как долго вы обычно блокируете свои элементы. Если это обычно кратковременные блокировки - просто используйте критические разделы, забудьте о сложных блокировках чтения-записи.

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

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

В Linux вам не следует самостоятельно реализовывать блокировки, а использовать posix_rwlock_t.

Имея массив 4096, такие элементы не должны представлять особых проблем.Структуры блокировки POSIX достаточно эффективно реализованы в Linux.В частности, они используют атомарные операции, когда это возможно, по «быстрому пути» и переходят к системным вызовам (особенно для FUTEX), когда имеется перегруженность этой конкретной блокировки.Поэтому, если вы реализуете относительно осторожно, так что любой поток удерживает только 1 или 2 блокировки одновременно, ограничение для Linux будет определяться только вашим общим числом рабочих потоков, а не количеством самих объектов.

1 голос
/ 06 августа 2011
  1. Это не рекомендуется. Вы не должны делать это на самом деле, потому что в Windows это будет потреблять 1 дескриптор объекта на семафор. Процесс может управлять только определенным количеством объектов Handles. Тема / Процесс и другие объекты Windows, возможно, потребуется использовать объекты Handle и будет потерпеть крах, если они не могут. Это похоже на Linux с Концепция файлового дескриптора.

  2. Разделите ваши 4096 элементов на 30 (например) наборов из 140 элементы и назначить каждой 140-группе один семафор. Тогда 30 (в этом примере) потоки будут пытаться получить доступ к этим 30 наборам и они будут синхронизированы на основе каждого семафора из 140 групп.

...