Какие решения существуют для циркулярных ссылок?

При использовании подсчета ссылок, какие возможные решения/методы имеют дело с циклическими ссылками?

Самое известное решение - использование слабых ссылок, однако многие статьи о предмете подразумевают, что существуют и другие методы, но повторяйте пример слабой ссылки. Что заставляет меня задаться вопросом, каковы эти другие методы?

  • Я не спрашиваю, каковы альтернативы подсчету ссылок, а что такое решения для циклических ссылок при использовании подсчета ссылок.

  • Этот вопрос касается не какой-либо конкретной проблемы/реализации/языка, а общего вопроса.

Ответы

Ответ 1

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

Изменить:

Можете ли вы расширить? Например, как вы будете иметь дело с отношением родитель-ребенок, когда ребенок должен знать о/доступ к родительскому? - OB OB

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

Тем не менее, если у вас должна быть структура данных tree/parent-child, в которой ребенок знает о родителе, вам придется реализовать свою собственную, ручно называемую последовательность слежения (т.е. внешнюю по отношению к любым деструкторам, которые вы можете реализовать), который начинается с корня (или на ветке, которую вы хотите обрезать), и выполняет поиск по глубине дерева для удаления ссылок из листьев.

Он становится сложным и громоздким, поэтому ИМО единственным решением является его полное исключение.

Ответ 2

Вот решение, которое я видел:

Добавьте метод к каждому объекту, чтобы сообщить ему, чтобы он выслал ссылки на другие объекты, например, назовите его Teardown().

Затем вы должны знать, кто "владеет" каждым объектом, а владелец объекта должен называть Teardown() на нем, когда они с ним выполняются.

Если существует круговая ссылка, скажем, что A ↔ B и C принадлежат A, то, когда вызывается C Teardown(), она вызывает A Teardown, которая вызывает Teardown on B, B затем отпускает ссылку на A, A затем отпускает свою ссылку на B (уничтожая B), а затем C отпускает свою ссылку на A (уничтожая A).

Ответ 3

Я предполагаю, что другой метод, используемый сборщиками мусора, является "отметкой и разверткой":

  • Установить флаг в каждом экземпляре объекта
  • Поверните график каждого экземпляра, доступного, очистив этот флаг
  • Каждый оставшийся экземпляр, который все еще имеет набор флагов, недоступен, даже если некоторые из этих экземпляров имеют круглые ссылки друг на друга.

Ответ 4

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

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

Аналогичным образом ссылки обмениваются группами с объектами или принадлежат нулевой группе.

Ссылка на объект влияет на счетчик ссылок группы (объекта), только если он (ссылка) является внешним по отношению к группе.

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

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

Ответ 5

Я всегда перепроектировал, чтобы избежать проблемы. Одним из распространенных случаев, когда это возникает, является родительская связь с ребенком, когда ребенок должен знать о родителе. Есть 2 решения для этого

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

  • Если родительский объект должен иметь доступ к дочерним элементам, тогда у него есть метод register, который принимает указатель, который не подсчитывается ссылкой, такой как указатель объекта, и соответствующий метод отмены регистрации. Ребенок должен будет вызвать метод регистрации и отмены регистрации. Когда родитель должен получить доступ к потомку, он набирает указатель объекта на подсчитанный интерфейс.

Ответ 6

При использовании подсчета ссылок, какие возможные решения/методы имеют дело с циклическими ссылками?

Три решения:

  • Augment наивный подсчет ссылок с помощью детектора цикла: подсчитывает декрементируется к ненулевым значениям считаются потенциальными источниками циклов и топология кучи вокруг них выполняется поиск циклов

    .
  • Увеличение наивного подсчета ссылок с помощью обычного сборщика мусора, такого как маркерная развертка.

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

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

Ответ 7

Я тоже ищу хорошее решение проблемы с круговой ссылкой.

i был кража заимствования API от World of Warcraft, посвященного достижениям. Я неявно переводил его в интерфейсы, когда понял, что у меня есть круговые ссылки.

Примечание: Вы можете заменить слово достижения ордерами, если вам не нравятся достижения. Но кто не любит достижения?

Здесь достижение:

IAchievement = interface(IUnknown)
   function GetName: string;
   function GetDescription: string;
   function GetPoints: Integer;
   function GetCompleted: Boolean;

   function GetCriteriaCount: Integer;
   function GetCriteria(Index: Integer): IAchievementCriteria;
end;

И затем список критериев достижения:

IAchievementCriteria = interface(IUnknown)
   function GetDescription: string;
   function GetCompleted: Boolean;
   function GetQuantity: Integer;
   function GetRequiredQuantity: Integer;
end;    

Все достижения регистрируются с центральным IAchievementController:

IAchievementController = interface
{
   procedure RegisterAchievement(Achievement: IAchievement);
   procedure UnregisterAchievement(Achievement: IAchievement);
}

Затем контроллер может быть использован для получения списка всех достижений:

IAchievementController = interface
{
   procedure RegisterAchievement(Achievement: IAchievement);
   procedure UnregisterAchievement(Achievement: IAchievement);

   function GetAchievementCount(): Integer;
   function GetAchievement(Index: Integer): IAchievement;
}

Идея заключалась в том, что, поскольку произошло что-то интересное, система будет называть IAchievementController и уведомлять о том, что что-то интересное происходит:

IAchievementController = interface
{
   ...
   procedure Notify(eventType: Integer; gParam: TGUID; nParam: Integer);
}

И когда происходит событие, контроллер будет проходить через каждый дочерний элемент и уведомлять об этом событие через свой метод Notify:

IAchievement = interface(IUnknown)
   function GetName: string;
   ...

   function GetCriteriaCount: Integer;
   function GetCriteria(Index: Integer): IAchievementCriteria;

   procedure Notify(eventType: Integer; gParam: TGUID; nParam: Integer);
end;

Если объект Achievement решает, что событие заинтересовало его, оно будет уведомлять его дочерние критерии:

IAchievementCriteria = interface(IUnknown)
   function GetDescription: string;
   ...
   procedure Notify(eventType: Integer; gParam: TGUID; nParam: Integer);
end;    

До сих пор график зависимостей всегда был сверху вниз:

 IAchievementController --> IAchievement --> IAchievementCriteria

Но что происходит, когда критерии достижения достигнуты? Объект Criteria должен был уведомить своего родителя "Достижение:

 IAchievementController --> IAchievement --> IAchievementCriteria
                                    ^                      |
                                    |                      |
                                    +----------------------+

Значение Criteria потребует ссылки на его родителя; кто сейчас ссылается друг на друга - утечка памяти.

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

 IAchievementController --> IAchievement --> IAchievementCriteria
      ^                      |    ^                      |
      |                      |    |                      |                                        
      +----------------------+    +----------------------+

Теперь Controller и его дочерний элемент Achievements обращаются друг к другу по кругу - больше утечек памяти.

Я думал, что, возможно, объект Criteria может вместо этого уведомить Controller, удалив ссылку на родителя. Но мы все еще имеем круговую ссылку, она занимает больше времени:

 IAchievementController --> IAchievement --> IAchievementCriteria
      ^                      |                          |
      |                      |                          |                                        
      +<---------------------+                          |
      |                                                 |
      +-------------------------------------------------+

Решение World of Warcraft

Теперь World of Warcraft api не является объектно-ориентированным. Но он разрешает любые циклические ссылки:

  • Не передавать ссылки на Controller. Имейте один, глобальный, singleton, Controller класс. Таким образом, достижение не должно ссылаться на контроллер, просто используйте его.

    Минусы: Делает тестирование и издевательское, невозможное - потому что вы должны иметь известную глобальную переменную.

  • Достижение не знает своего списка критериев. Если вы хотите Criteria для Achievement, вы спросите Controller для них:

     IAchievementController = interface(IUnknown)
         function GetAchievementCriteriaCount(AchievementGUID: TGUID): Integer;
         function GetAchievementCriteria(Index: Integer): IAchievementCriteria;
     end;
    

    Минусы: Achievement больше не может передавать уведомления ему Criteria, потому что у него нет никаких критериев. Теперь вам нужно зарегистрировать Criteria с помощью Controller

  • Когда a Criteria завершено, он уведомляет Controller, который уведомляет Achievement:

     IAchievementController-->IAchievement      IAchievementCriteria
          ^                                              |
          |                                              |
          +----------------------------------------------+
    

    Минусы: У меня болит голова.

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

Но, как и вы, возможно, есть лучший способ.

Ответ 8

Поместите вещи в иерархию

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

Работа с деревьями

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

Эмулирующая сборка мусора

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

Используйте уникальные указатели, необработанные указатели и значения

В реальном мире я обнаружил, что фактические варианты использования общих указателей очень ограничены, и их следует избегать в пользу уникальных указателей, необработанных указателей или - даже лучше - только типов значений. Общие указатели обычно используются, когда у вас есть несколько ссылок, указывающих на общую переменную. Совместное использование вызывает трения и противоречия, и их следует избегать, если это возможно, в первую очередь. Уникальные указатели и не владеющие исходными указателями и/или значениями гораздо проще рассуждать. Однако иногда нужны общие указатели. Общие указатели также используются для продления срока службы объекта. Это обычно не приводит к циклическим ссылкам.

Нижняя строка

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

Ответ 9

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

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

У Python есть сборщик, который делает это, как и PHP.

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

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

Некоторые документы: "Вниз для графа? Получение ссылки, отсчитывающей назад в кольце" Рифат Шахрияр, Стивен М. Блэкберн и Дэниел Фрэмптон http://users.cecs.anu.edu.au/~steveb/downloads/pdf/rc-ismm-2012.pdf   "Единая теория сбора мусора" Дэвида Ф. Бэкона, Перри Ченга и В.Т. Rajan http://www.cs.virginia.edu/~cs415/reading/bacon-garbage.pdf

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

Ответ 10

Если вам нужно сохранить циклические данные, для snapShot в строку,

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

Шаг 1: При анализе данных в строку JSON я выталкиваю любой object.is_cyclic, который не использовался в массиве и сохраняет индекс в строке. (Любые используемые объекты заменяются существующим индексом).

Шаг 2: Я пересекаю массив объектов, устанавливая любые children.is_cyclic в указанный индекс или нажимая на новый объект в массив. Затем разбор массива в строку JSON.

ПРИМЕЧАНИЕ. Нажимая новые циклические объекты в конец массива, принудительная рекурсия будет удалена до тех пор, пока не будут удалены все циклические ссылки.

Шаг 3: В последний раз я разбираю строки JSON в одну строку:

Вот скрипка javascript... https://jsfiddle.net/7uondjhe/5/

function my_json(item) {

var parse_key = 'restore_reference', 
    stringify_key = 'is_cyclic';

var referenced_array = [];


var json_replacer = function(key,value) {

            if(typeof value == 'object' && value[stringify_key]) {
                var index = referenced_array.indexOf(value);

                if(index == -1) {
                    index = referenced_array.length;
                    referenced_array.push(value);
                };

                return {
                    [parse_key]: index
                }
            }
            return value;
}

var json_reviver = function(key, value) {

        if(typeof value == 'object' && value[parse_key] >= 0) {
                return referenced_array[value[parse_key]];
        }
        return value;
}
var unflatten_recursive = function(item, level) {
        if(!level) level = 1;
        for(var key in item) {
            if(!item.hasOwnProperty(key)) continue;
            var value = item[key];

            if(typeof value !== 'object') continue;

            if(level < 2 || !value.hasOwnProperty(parse_key)) {
                unflatten_recursive(value, level+1);
                continue;
            }

            var index = value[parse_key];
            item[key] = referenced_array[index];

        }
};


var flatten_recursive = function(item, level) {
        if(!level) level = 1;
        for(var key in item) {
            if(!item.hasOwnProperty(key)) continue;
            var value = item[key];

            if(typeof value !== 'object') continue;

            if(level < 2 || !value[stringify_key]) {
                flatten_recursive(value, level+1);
                continue;
            }

            var index = referenced_array.indexOf(value);

            if(index == -1) (item[key] = {})[parse_key] = referenced_array.push(value)-1; 
            else (item[key] = {})[parse_key] = index;
        }
};


return {

    clone: function(){ 
        return JSON.parse(JSON.stringify(item,json_replacer),json_reviver)
},

    parse: function() {
            var object_of_json_strings = JSON.parse(item);
            referenced_array = JSON.parse(object_of_json_strings.references);
            unflatten_recursive(referenced_array);
            return JSON.parse(object_of_json_strings.data,json_reviver);
    },

    stringify: function() {
            var data = JSON.stringify(item,json_replacer);
            flatten_recursive(referenced_array);
            return JSON.stringify({
                        data: data,
                        references: JSON.stringify(referenced_array)
                });
    }
}
}

Ответ 11

Есть несколько способов, которыми я знаю, чтобы обойти это:

Первый (и предпочтительный) - это просто извлечение общего кода в третью сборку и использование обеих ссылок для этого

Вторая добавляет ссылку как "Ссылка на файл" (dll) вместо "Ссылка на проект"

Надеюсь, что это поможет