Из Википедии, бесплатной энциклопедии
Перейти к навигации Перейти к поиску

В компьютерном программировании , управление ресурсами относится к методам управления Ресурсы (компоненты с ограниченной доступностью).

Компьютерные программы могут управлять своими собственными ресурсами [ какие? ] , используя функции, предоставляемые языками программирования ( Elder, Jackson & Liblit (2008) - обзорная статья, в которой противопоставляются различные подходы), или может выбрать управление ими с помощью хоста - операционной системы или виртуальной машины - или другой программы.

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

Контроль доступа [ править ]

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

Управление ресурсами стремится контролировать доступ, чтобы предотвратить обе эти ситуации.

Утечка ресурсов [ править ]

Формально управление ресурсами (предотвращение утечки ресурсов) состоит в обеспечении того, чтобы ресурс был освобожден тогда и только тогда, когда он был успешно получен. Эту общую проблему можно абстрагировать как код « до, основной и после », которые обычно выполняются в этом порядке, с условием, что код « после» вызывается тогда и только тогда, когда код « до» успешно завершается, независимо от того, выполняется ли основной код выполняется успешно или нет. Это также известно как выполнение около [1] или сэндвич кода и происходит в различных других контекстах [2], таких как временное изменение состояния программы или трассировкавход и выход из подпрограммы . Однако наиболее часто упоминаемое приложение - управление ресурсами. В аспектно-ориентированном программировании такое выполнение логики является формой совета .

В терминологии анализа потока управления высвобождение ресурса должно предшествовать успешному приобретению ресурса; [3] неспособность гарантировать, что это ошибка, и путь кода, который нарушает это условие, вызывает утечку ресурсов. Утечки ресурсов часто являются незначительными проблемами, обычно не приводят к сбою программы, а вызывают некоторое замедление работы программы или всей системы. [2] Однако они могут вызывать сбои - как самой программы, так и других программ - из-за исчерпания ресурсов: если в системе не хватает ресурсов, запросы на получение не выполняются. Это может представлять ошибку безопасностиесли атака может вызвать истощение ресурсов. Утечки ресурсов могут происходить при обычном выполнении программы - например, когда просто забывают освободить ресурс - или только в исключительных обстоятельствах, например, когда ресурс не освобождается, если в другой части программы есть исключение. Утечки ресурсов очень часто вызываются преждевременным выходом из подпрограммы либо returnоператором, либо исключением, вызванным либо самой подпрограммой, либо более глубокой подпрограммой, которую она вызывает. В то время как освобождение ресурсов из-за операторов возврата может обрабатываться осторожным освобождением внутри подпрограммы перед возвратом, исключения не могут быть обработаны без некоторой дополнительной языковой возможности, которая гарантирует выполнение кода выпуска.

Говоря более тонко, успешное приобретение ресурсов должно преобладатьосвобождение ресурса, так как в противном случае код попытается освободить ресурс, который он не получил. Последствия такого неправильного выпуска варьируются от молчаливого игнорирования до сбоя программы или непредсказуемого поведения. Эти ошибки обычно проявляются редко, так как они требуют выделения ресурсов для первого сбоя, что обычно является исключительным случаем. Кроме того, последствия могут быть несерьезными, поскольку программа уже может давать сбой из-за невозможности получить важный ресурс. Однако они могут предотвратить восстановление после сбоя или превратить плановое отключение в аварийное отключение. Это условие обычно обеспечивается путем первой проверки того, что ресурс был успешно получен перед его освобождением, либо с помощью логической переменной для записи «успешно получен»- которому не хватает атомарности, если ресурс получен, но переменная флага не может быть обновлена, или наоборот - или дескриптором ресурса являетсяТип , допускающий значение NULL , где «NULL» означает «не удалось получить», что обеспечивает атомарность.

Конфликт за ресурсы [ править ]

Управление памятью [ править ]

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

Лексическое управление и явное управление [ править ]

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

Основные техники [ править ]

Базовый подход к управлению ресурсами заключается в том, чтобы получить ресурс, что-то с ним сделать, а затем выпустить его, получив код формы (проиллюстрирован открытием файла в Python):

f  =  open ( имя файла ) ... f . закрыть ()

Это правильно, если промежуточный ...код не содержит раннего exit ( return), язык не имеет исключений и openгарантированно завершится успешно. Однако это вызывает утечку ресурсов, если есть возврат или исключение, и вызывает неправильное освобождение незанятого ресурса, если openможет произойти сбой.

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

Утечка ресурсов может быть устранена на языках, которые поддерживают finallyконструкцию (например, Python), поместив тело в tryпредложение, а релиз - в finallyпредложение:

f  =  open ( имя файла ) попробуйте :  ... наконец :  f . закрыть ()

Это гарантирует правильное освобождение, даже если есть возврат в теле или возникшее исключение. Кроме того, обратите внимание , что приобретение происходит до того в tryстатье, гарантируя , что finallyусловие выполняется только если openкод успешно (без выбрасывания исключения), предполагая , что «не исключение» означает «успех» (как в случае openв Python). Если получение ресурса может завершиться неудачно без создания исключения, например, при возврате формы null, это также необходимо проверить перед выпуском, например:

f  =  open ( имя файла ) попробуйте :  ... наконец :  если  f :  f . закрыть ()

Хотя это обеспечивает правильное управление ресурсами, оно не обеспечивает смежности или инкапсуляции. Во многих языках есть механизмы, обеспечивающие инкапсуляцию, такие как withинструкция в Python:

с  open ( имя файла )  как  f :  ...

Вышеуказанные методы - разматывают защиты ( finally) и некоторые формы инкапсуляции - наиболее общий подход к управлению ресурсами, находится в различных формах в C #, Common Lisp , Java, Python, Ruby, Scheme и Smalltalk , [1] среди прочих; они датируются концом 1970-х на диалекте NIL Lisp; см. Обработка исключений § История . Существует множество вариантов реализации, а также существенно разные подходы .

Подходы [ править ]

Защита от перемотки [ править ]

Наиболее распространенный подход к управлению ресурсами в разных языках - использование защиты от откручивания, которая вызывается, когда выполнение выходит из области видимости - путем выполнения, выполняемого за пределами конца блока, возврата изнутри блока или генерирования исключения. Это работает для ресурсов, управляемых стеком, и реализовано на многих языках, включая C #, Common Lisp, Java, Python, Ruby и Scheme. Основные проблемы с этим подходом заключаются в том, что код выпуска (чаще всего в finallyпредложении) может быть очень далек от кода получения (ему не хватает смежности ), и что код получения и выпуска всегда должен быть соединен вызывающим пользователем (в нем отсутствует инкапсуляция). Их можно исправить либо функционально, используя замыкания / обратные вызовы / сопрограммы (Common Lisp, Ruby, Scheme), либо используя объект, который обрабатывает как получение, так и выпуск, и добавляя языковую конструкцию для вызова этих методов при входе и выходе управления. область видимости (C # using, Java try-with-resources, Python with); Смотри ниже.

Альтернативный, более императивный подход - написать асинхронный код в прямом стиле : получить ресурс, а затем в следующей строке отложить выпуск, который вызывается при выходе из области действия - синхронное получение с последующим асинхронным выпуском. Он возник в C ++ как класс ScopeGuard, Андрей Александреску и Петру Марджинян в 2000 году [4] с улучшениями Джошуа Лерера, [5] и имеет прямую языковую поддержку в D через scopeключевое слово ( ScopeGuardStatement ), где это один из подходов к безопасность исключений в дополнение к RAII (см. ниже). [6] Он также был включен в Go, посколькуdeferутверждение. [7] В этом подходе отсутствует инкапсуляция - необходимо явно сопоставлять получение и выпуск - но избегает необходимости создавать объект для каждого ресурса (с точки зрения кода, избегайте написания класса для каждого типа ресурса).

Объектно-ориентированное программирование [ править ]

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

Во - первых, это вопрос о собственности: это объект имеет ресурс?

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

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

  • Получение / выпуск, пока объект действителен, с помощью методов (экземпляра), таких как openили dispose.
  • Получение / выпуск во время создания / уничтожения объекта (в инициализаторе и финализаторе).
  • Ни получать, ни освобождать ресурс, вместо этого просто иметь представление или ссылку на ресурс, управляемый извне для объекта, как при внедрении зависимости ; конкретно, объект, у которого есть ресурс (или может связываться с тем, у которого есть ресурс) передается в качестве аргумента методу или конструктору.

Наиболее распространенным является получение ресурса во время создания объекта, а затем его явное освобождение с помощью обычно вызываемого метода экземпляра dispose. Это аналогично традиционному управлению файлами (получение во время open, явное освобождение close) и известно как шаблон удаления . Это основной подход, используемый в нескольких основных современных объектно-ориентированных языках, включая Java , C # и Python , и в этих языках есть дополнительные конструкции для автоматизации управления ресурсами. Однако даже в этих языках более сложные отношения объектов приводят к более сложному управлению ресурсами, как обсуждается ниже.

RAII [ править ]

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

RAII - это стандартный подход к управлению ресурсами в C ++, но он мало используется за пределами C ++, несмотря на его привлекательность, потому что он плохо работает с современным автоматическим управлением памятью, в частности с отслеживанием сборки мусора : RAII связывает управление ресурсами с управлением памятью, но у них есть существенные различия . Во-первых, поскольку ресурсы дороги, их желательно освободить как можно скорее, поэтому объекты, содержащие ресурсы, должны уничтожаться, как только они становятся мусором (больше не используются). Уничтожение объектов происходит быстро при детерминированном управлении памятью, например, в C ++ (объекты, выделенные стеком, уничтожаются при раскручивании стека, объекты, выделенные в куче, уничтожаются вручную посредством вызова deleteили автоматически с использованиемunique_ptr) или при детерминированном подсчете ссылок (где объекты уничтожаются немедленно, когда их счетчик ссылок падает до 0), и поэтому RAII хорошо работает в этих ситуациях. Однако большинство современных систем автоматического управления памятью не является детерминированным и не дает никаких гарантий, что объекты будут уничтожены быстро или даже вообще! Это потому, что дешевле оставить выделенный мусор, чем точно собирать каждый объект сразу после того, как он становится мусором. Во-вторых, освобождение ресурсов во время уничтожения объекта означает, что у объекта должен быть финализатор (в детерминированном управлении памятью, известный как деструктор ) - объект не может быть просто освобожден, что значительно усложняет и замедляет сборку мусора.

Сложные отношения [ править ]

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

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

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

С точки зрения реализации в композиции объектов, если используется шаблон удаления, объект-владелец, таким образом, также будет иметь disposeметод, который, в свою очередь, вызывает disposeметоды принадлежащих объектов, которые должны быть удалены; в RAII это обрабатывается автоматически (до тех пор, пока принадлежащие объекты сами автоматически уничтожаются: в C ++, если они являются значением или a unique_ptr, но не необработанным указателем: см. владение указателем ). При агрегации объектов просматривающему объекту ничего не нужно делать, поскольку он не отвечает за ресурс.

Оба обычно встречаются. Например, в библиотеке классов Java , Reader#close()закрывает основной поток, и они могут быть соединены. Например, a BufferedReaderможет содержать a InputStreamReader, который, в свою очередь, содержит a FileInputStream, и вызов closeэтого, BufferedReaderв свою очередь, закрывает InputStreamReader, который, в свою очередь, закрывает FileInputStream, что, в свою очередь, освобождает ресурс системного файла. Действительно, объект, который напрямую использует ресурс, может быть даже анонимным благодаря инкапсуляции:

try  ( BufferedReader  reader  =  new  BufferedReader ( new  InputStreamReader ( new  FileInputStream ( fileName ))))  {  // Используем читатель. } // считыватель закрывается при выходе из блока try-with-resources, который последовательно закрывает каждый из содержащихся в нем объектов.

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

попробуйте  ( FileInputStream  stream  =  новый  FileInputStream ( fileName ))))  {  BufferedReader  reader  =  новый  BufferedReader ( новый  InputStreamReader ( поток ));  // Используем читателя. } // поток закрывается при выходе из блока try-with-resources. // Читатель больше не может использоваться после закрытия потока, но пока он не выходит из блока, это не проблема.

Напротив, в Python csv.reader не владеет тем, fileчто он читает, поэтому нет необходимости (и это невозможно) закрывать считыватель, и вместо этого fileдолжен быть закрыт сам. [8]

с  open ( имя файла )  как  f :  r  =  csv . reader ( f )  # Используйте r. # f закрывается при выходе из оператора with и больше не может использоваться. # С r ничего не делается, но лежащая в основе f закрыта, поэтому r также нельзя использовать.

В .NET соглашение предусматривает, что ответственность за это несет только непосредственный пользователь ресурсов: «Вы должны реализовывать IDisposable только в том случае, если ваш тип напрямую использует неуправляемые ресурсы». [9]

В случае более сложного графа объектов , такого как несколько объектов, совместно использующих ресурс, или циклы между объектами, которые содержат ресурсы, правильное управление ресурсами может быть довольно сложным, и возникают точно такие же проблемы, как и при финализации объекта (через деструкторы или финализаторы); например, проблема пропущенного слушателя может возникнуть и вызвать утечку ресурсов при использовании шаблона наблюдателя (а наблюдатели удерживают ресурсы). Существуют различные механизмы, позволяющие лучше контролировать управление ресурсами. Например, в Closure Library Google , то goog.Disposableкласс предоставляет registerDisposableметод для регистрации других объектов , которые будут расположен с этим объектом, вместе с различными методами экземпляра класса и более низкого уровня для управления утилизацией.

Структурированное программирование [ править ]

В структурированном программировании управление ресурсами стека осуществляется простым вложением кода, достаточным для обработки всех случаев. Это требует только одного возврата в конце кода и может привести к сильно вложенному коду, если необходимо получить много ресурсов, что некоторыми считается анти-паттерном - Arrow Anti Pattern , [10] из-за треугольной формы. от последовательного гнездования.

Положение об очистке [ править ]

Еще один подход, который допускает ранний возврат, но объединяет очистку в одном месте, состоит в том, чтобы иметь единственный выход из функции, которому предшествует код очистки, и использовать goto для перехода к очистке перед выходом. Это нечасто встречается в современном коде, но встречается в некоторых случаях использования C.

См. Также [ править ]

  • Управление памятью
  • Пул (информатика)

Ссылки [ править ]

  1. ^ a b Бек 1997 , стр. 37–39.
  2. ^ Б старейшина Джексон и Liblit 2008 , стр. 3.
  3. ^ Старший, Джексон и Liblit 2008 , стр. 2.
  4. ^ " Generic: измените способ написания безопасного для исключений кода - навсегда ", Андрея Александреску и Петру Марджинян, 1 декабря 2000 г., Dr. Dobb's
  5. ^ ScopeGuard 2.0 , Джошуа Лерер
  6. ^ D: Безопасность исключений
  7. ^ Defer, паника, и восстановление , Эндрю Gerrand, The Go блог, 4 августа 2010
  8. ^ Python: нет csv.close ()?
  9. ^ "IDisposable Interface" . Проверено 3 апреля 2016 .
  10. ^ Сведение Arrow Код , Jeff Atwood, 10 января 2006
  • Бек, Кент (1997). Шаблоны передовой практики Smalltalk . Прентис Холл. ISBN 978-0134769042.
  • Старейшина, Мэтт; Джексон, Стив; Либлит, Бен (октябрь 2008 г.). Code Sandwiches (PDF) (Технический отчет). Университет Висконсина-Мэдисона . 1647, аннотация

Дальнейшее чтение [ править ]

  • Обновление DG: утилизация, доработка и управление ресурсами , Джо Даффи

Внешние ссылки [ править ]

  • Детерминированное управление ресурсами , WikiWikiWeb