Какова наиболее эффективная реализация java-подобного монитора объектов на C++?

StackOverflow https://stackoverflow.com/questions/1752897

Вопрос

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

При портировании на C++ какая реализация будет для этого лучшей.Я думаю, что должно быть что-то лучше, чем «pthread_mutex_init», или накладные расходы на объект в Java действительно настолько высоки?

Редактировать:я только что проверил, что pthread_mutex_t в Linux i386 имеет размер 24 байта.Это здорово, если мне придется зарезервировать это место для каждого объекта.

Это было полезно?

Решение

JVM Sun Hotspot реализует тонкий замки с помощью сравнить и поменять местами.Если объект заблокирован, то ожидающий поток ожидает монитора потока, который заблокировал объект.Это означает, что вам понадобится только одна тяжелая блокировка для каждого потока.

Другие советы

В каком-то смысле это хуже, чем pthread_mutex_init, на самом деле.Из-за ожидания/уведомления в Java вам как бы нужна парная переменная мьютекса и условия для реализации монитора.

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

Одним из наблюдений является то, что не каждый объект должен иметь собственный монитор.Объект, который в данный момент не синхронизирован, в нем не нуждается.Таким образом, JVM может создать пул мониторов, и каждый объект может иметь просто поле указателя, которое заполняется, когда поток действительно хочет синхронизироваться с объектом (например, с помощью специфичной для платформы операции атомарного сравнения и обмена).Таким образом, стоимость инициализации монитора не должна прибавляться к стоимости создания объекта.Предполагая, что память предварительно очищена, создание объекта может быть:уменьшить указатель (плюс некоторую проверку границ с ветвью с предсказанием-ложью для кода, который запускает gc и т. д.);заполните тип;вызвать наиболее производный конструктор.Я думаю, можно сделать так, чтобы конструктор Object ничего не делал, но, очевидно, многое зависит от реализации.

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

Я не уверен, как Java это делает, но .NET не сохраняет мьютекс (или его аналог - структура, которая его удерживает, называется "syncblk") непосредственно в объекте.Скорее, у него есть глобальная таблица syncblk, и объект ссылается на свой syncblk по индексу в этой таблице.Более того, объекты не получают синхронизацию сразу после создания — вместо этого она создается по требованию при первой блокировке.

Я предполагаю (заметьте, я не знаю, как он на самом деле это делает!), что он использует атомарное сравнение и обмен для связывания объекта и его syncblk потокобезопасным способом:

  1. Проверьте скрытое syncblk_index поле нашего объекта на 0.Если он не 0, заблокируйте его и продолжайте, иначе...
  2. Создайте новый syncblk в глобальной таблице, получите для него индекс (глобальные блокировки приобретаются/снимаются здесь по мере необходимости).
  3. Сравнение и обмен для записи его в сам объект.
  4. Если предыдущее значение было 0 (предположим, что 0 не является допустимым индексом и является начальным значением для скрытого syncblk_index поле наших объектов), наше создание syncblk не оспаривалось.Зафиксируйте его и продолжайте.
  5. Если предыдущее значение не было 0, то кто-то другой уже создал syncblk и связал его с объектом, пока мы создавали свой, и теперь у нас есть индекс этого syncblk.Удалите тот, который мы только что создали, и заблокируйте тот, который мы получили.

Таким образом, накладные расходы на каждый объект в лучшем случае составляют 4 байта (при условии, что индексы в таблице syncblk 32-битные), но больше для объектов, которые фактически заблокированы.Если вы редко блокируете свои объекты, то эта схема выглядит хорошим способом сократить использование ресурсов.Но если в конечном итоге вам понадобится заблокировать большинство или все ваши объекты, сохранение мьютекса непосредственно внутри объекта может быть быстрее.

Наверняка вам не нужен такой монитор для каждый объект!

При портировании с Java на C++ мне кажется плохой идеей просто копировать все вслепую.Лучшая структура для Java — это не то же самое, что лучшая структура для C++, не в последнюю очередь потому, что в Java есть сборка мусора, а в C++ — нет.

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

Я бы использовал библиотеку потоков boost или новую стандартную библиотеку потоков C++0x для переносимости, а не полагался на особенности платформы на каждом шагу. Boost.Thread поддерживает Linux, MacOSX, win32, Solaris, HP-UX и другие.Мой реализация библиотеки потоков C++0x в настоящее время поддерживает только Windows и Linux, но со временем станут доступны и другие реализации.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top