Объекты

From SunFlurry wiki
Jump to: navigation, search

Общая информация

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

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


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

a:=List.Create();			//Новый сложный объект присваивается переменной "а"
Message(RefCount(a));			//Возвращает 1 (объект присвоен одной переменной)
b:=List.Create(a,a);			//Создается еще один список, двумя элементами которого являются ссылки на первый список ("a")
Message(RefCount(a));			//Возвращает 3 (объект присвоен одной переменной и хранится в двух элементах списка)
a:=1;					//Переменной присваивается новое значение
Message(RefCount(b.Get(1)));		//Возвращает 2 (объект хранится в двух элементах списка)
b.Clear();				//Все ссылки на объект удалены, сам объект также удаляется из системы.

Особенности работы со строками

Строки в системе могут иметь достаточно большие размеры (до 2 млрд. символов), поэтому, их копирование, как обычное копирование простых объектов, не производится. Строки обладают частично характеристиками простых объектов, частично характеристиками сложных. Каждая строка, как сложные объекты, имеет внутренний счетчик ссылок. При копировании строки (присваивании ее, к примеру, другой переменной, или добавлении в список) в памяти создается только ссылка на нее. Когда же к строке будет добавлена какая-нибудь подстрока, строка будет скопирована, и в памяти окажутся две строки -- одна оригинальная, принадлежащая другим переменным или списку, и одна с добавлением, принадлежащая переменной, к которой добавлялась подстрока. Такая система позволяет значительно ускорить работу со строками и уменьшить потребность в памяти. Некоторые функции работы со строками не создают копии строк даже в случае манипуляции над ними, для еще большей оптимизации работы. Нужно заметить, что для системы строки являются все-таки простыми объектами, поэтому, функция RefСount будет возвращать для них значение -1. Пример, объясняющий работу строк в памяти см. ниже:

a:="Строка";				//Переменной "a" присвоена UTF-16 строка
b:=a;					//Переменные "a" и "b" ссылаются на одну и ту же строку, в памяти имеется одна копия этой строки.
c:=a+", Строка 2";			//Переменные "a" и "b" ссылаются на одну строку, однако переменная "c" содержит уже созданную заново строку
a:=0;
b:=0;					//Первая строка удаляется из системы
d:=Mid(c,9);				//Переменные "c" и "d" ссылаются на одну и ту же строку, однако "c" имеет значение "Строка, Строка 2", тогда как "d" имеет значение "Строка 2"

При работе со строками нужно также иметь в виду, что в системе присутствует два вида строк в кодировках Ansi и UTF-16. Для более подробной информации см. Строки.

Ссылки на самого себя

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

//Некорректный способ освобождения памяти при ссылках на самого себя
a:=List.Create();			//Новый сложный объект присваивается переменной "а"
Message(RefCount(a));			//Возвращает 1 (объект присвоен одной переменной)
a.Add(1);				//К списку добавляется первый элемент (1)
a.Add(a);				//К списку добавляется второй элемент, являющийся ссылкой на сам список
Message(RefCount(a));			//Возвращает 2 (объект присвоен одной переменной и хранится в одном элементе списка)
...					//Иные действия программы
a:=1;					//Переменной присваивается новое значение, список остается в памяти, но доступ к нему утерян. Произошла утечка памяти.

//Корректный способ освобождения памяти при ссылках на самого себя
a:=List.Create();			//Новый сложный объект присваивается переменной "а"
Message(RefCount(a));			//Возвращает 1 (объект присвоен одной переменной)
a.Add(1);				//К списку добавляется первый элемент (1)
Try					//Создание конструкции отлова исключения, чтобы убедиться, что из-за исключительной ситуации, не произойдет утечки памяти
  a.Add(a);				//К списку добавляется второй элемент, являющийся ссылкой на сам список
  Message(RefCount(a));			//Возвращает 2 (объект присвоен одной переменной и хранится в одном элементе списка)
  ...					//Иные действия программы
Finally
  SetMultiThreaded(a);			//Установка флага многопоточности (это необходимо из-за внутреннего механизма блокировок объектов)
  a.Clear();				//Очистка списка, ситуация утечки более невозможна
EndTry;
Message(RefCount(a));			//Возвращает 1 (объект присвоен одной переменной)
a:=1;					//Переменной присваивается новое значение, список удаляется из памяти

Многопоточные и однопоточные объекты, внутренний механизм блокировки

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

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