Как эффективно отладить проблемы подсчета ссылок в разделяемой памяти? - PullRequest
5 голосов
/ 08 февраля 2010

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

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

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

(Существует более «человеческое» решение этой проблемы, например, обеспечение того, чтобы все приложения использовали одну и ту же библиотеку для доступа к области общей памяти, но если общая область рассматривается как двоичный интерфейс, и не все процессы будут приложения, написанные вами вне вашего контроля.Кроме того, даже если все приложения используют одну и ту же библиотеку, одно приложение может иметь ошибку вне библиотеки, приводящую к повреждению памяти таким образом, что предотвращается уменьшение счетчика. Да, я использую небезопасный язык, такой как C / C ++;)

Редактировать: В ситуациях с одним процессом у вас будет контроль, поэтому вы можете использовать RAII (в C ++).

Ответы [ 5 ]

7 голосов
/ 08 февраля 2010

Вы можете сделать это, используя только одно дополнительное целое число на объект.

Инициализировать целое число до нуля. Когда процесс увеличивает счетчик ссылок для объекта, он XOR его PID в целое число:

object.tracker ^= self.pid;

Когда процесс уменьшает счетчик ссылок, он делает то же самое.

Если счетчик ссылок всегда равен 1, то целое число в трекере будет равно PID процесса, который увеличил его, но не уменьшил его.


Это работает, потому что XOR является коммутативным ((A ^ B) ^ C == A ^ (B ^ C)), поэтому, если процесс XOR отслеживает трекер с собственным PID четное число раз, это то же самое, что XOR с PID ^ PID - это ноль , который не влияет на значение трекера.

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

1 голос
/ 08 февраля 2010

Наиболее эффективные системы отслеживания владения ресурсами даже не используют счетчики ссылок, не говоря уже о списках владельцев ссылок. Они просто имеют статическую информацию о разметках каждого типа данных, которые могут существовать в памяти, а также о форме фрейма стека для каждой функции, и у каждого объекта есть индикатор типа. Таким образом, инструмент отладки может сканировать стек каждого потока и рекурсивно прослеживать ссылки на объекты, пока не получит карту всех объектов в памяти и того, как они ссылаются друг на друга. Но, конечно, системы, которые имеют такую ​​возможность, в любом случае также имеют автоматический сбор мусора. Им нужна помощь от компилятора, чтобы получить всю эту информацию о расположении объектов и фреймах стека, и такая информация на самом деле не может быть надежно получена из C / C ++ во всех случаях (поскольку ссылки на объекты могут храниться в объединениях и т. Д.). плюс, они работают намного лучше, чем подсчет ссылок во время выполнения.

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

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

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

Я бы сказал, что если вы используете разделяемую память достаточно сложно, чтобы нуждаться в чем-то похожем на GC, то у вас почти худшее из обоих миров: дорогое создание процесса без преимуществ изоляции процесса. Вы написали (по сути) многопоточное приложение, в котором вы разделяете изменяемые объекты между потоками.

Локальные сокеты являются очень кроссплатформенным и очень быстрым API для межпроцессного взаимодействия; единственный, который работает в основном одинаково на всех Unices и Windows. Поэтому рассмотрите возможность использования этого в качестве минимального канала связи.

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

1 голос
/ 08 февраля 2010

По сути, совместное использование разделяемой памяти не является надежным решением, и я не знаю, как сделать его устойчивым.

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

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

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

Я рекомендую вам использовать каналы или другие примитивы для передачи сообщений между каждым процессом (иногда существуют естественные отношения «главный-подчиненный», в других случаях все должны общаться со всеми). Затем вы воспользуетесь тем, что операционная система закрывает эти соединения, когда процесс умирает, и ваши коллеги получают сигнал в этом событии. Кроме того, вы можете использовать сообщения о таймауте ping / pong, чтобы определить, завис ли одноранговый узел.

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

0 голосов
/ 08 февраля 2010

Рядом с самим делом: вы также можете использовать какой-нибудь инструмент, такой как AQTime, с подсчетом ссылок с подсчетом ссылок.

0 голосов
/ 08 февраля 2010

Использовать следующие

int pids[MAX_PROCS]
int counter;

Increment

do
   find i such pid[i]=0  // optimistic
while(cas[pids[i],0,mypid)==false)
my_pos = i;
atomic_inc(counter)

Decrement

pids[my_pos]=0
atomic_dec(counter);

Итак, вы знаете все процессы, использующие этот объект

Вы MAX_PROCS достаточно большой и ищите бесплатно разместить случайным образом, так что если число процессов значительно ниже, чем MAX_PROCS поиск будет очень быстро.

...