Управление ресурсами (вычисления) - Resource management (computing)

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

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

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

Контроль доступа

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

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

Утечка ресурсов

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

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

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

Спор за ресурсы

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

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

Лексическое управление и явное управление

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

Базовые техники

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

ж = открыто(имя файла)...ж.Закрыть()

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

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

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

ж = открыто(имя файла)пытаться:    ...наконец-то:    ж.Закрыть()

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

ж = открыто(имя файла)пытаться:    ...наконец-то:    если ж:        ж.Закрыть()

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

с открыто(имя файла) так как ж:    ...

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

Подходы

Расслабьтесь защита

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

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

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

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

Во-первых, возникает вопрос о праве собственности: действительно ли объект имеют ресурс?

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

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

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

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

RAII

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

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

Сложные отношения

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Положение об очистке

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

Смотрите также

Рекомендации

  1. ^ а б Бек 1997 С. 37–39.
  2. ^ а б Старейшина, Джексон и Либлит, 2008 г., п. 3.
  3. ^ Старейшина, Джексон и Либлит, 2008 г., п. 2.
  4. ^ "Generic: измените способ написания безопасного для исключений кода - навсегда ", к Андрей Александреску и Петру Маргинян, 01 декабря 2000 г., Доктора Добба
  5. ^ ScopeGuard 2.0, Джошуа Лерер
  6. ^ D: Безопасность исключений
  7. ^ Отсрочка, паника и восстановление, Эндрю Герранд, Блог Go, 4 августа 2010 г.
  8. ^ Python: нет csv.close ()?
  9. ^ "IDisposable Интерфейс". Получено 2016-04-03.
  10. ^ Код стрелки сглаживания, Джефф Этвуд, 10 января 2006 г.

дальнейшее чтение

внешняя ссылка