Управление ресурсами (вычисления)
В компьютерном программировании под управлением ресурсами понимаются методы управления ресурсами (компонентами с ограниченной доступностью).
Компьютерные программы могут управлять своими собственными ресурсами. [ который? ] используя функции, предоставляемые языками программирования ( Elder, Jackson & Liblit (2008) — это обзорная статья, в которой сравниваются различные подходы), или можно выбрать управление ими с помощью хоста — операционной системы или виртуальной машины — или другой программы.
Управление на основе хоста известно как отслеживание ресурсов и заключается в устранении утечек ресурсов: прекращении доступа к ресурсам, которые были получены, но не освобождены после использования. Это называется освобождением ресурсов и аналогично сборке мусора в памяти. Во многих системах операционная система освобождает ресурсы после того, как процесс выполняет выхода системный вызов .
Контроль доступа [ править ]
Отсутствие освобождения ресурса после того, как программа завершила его использование, известно как утечка ресурса и является проблемой в последовательных вычислениях. Множественные процессы, желающие получить доступ к ограниченному ресурсу, могут стать проблемой при параллельных вычислениях и известны как конкуренция за ресурсы .
Управление ресурсами стремится контролировать доступ, чтобы предотвратить обе эти ситуации.
Утечка ресурсов [ править ]
Формально управление ресурсами (предотвращение утечек ресурсов) состоит в обеспечении освобождения ресурса тогда и только тогда, когда он успешно получен. Эту общую проблему можно абстрагировать как код « до, тело и после », которые обычно выполняются в этом порядке, с условием, что код после вызывается тогда и только тогда, когда код перед успешно завершается, независимо от того, тела код выполняется успешно или нет. Это также известно как выполнение вокруг [1] или сэндвич кода и встречается в различных других контекстах, [2] например, временное изменение состояния программы или отслеживание входа и выхода из подпрограммы . Однако управление ресурсами является наиболее часто упоминаемым приложением. В аспектно-ориентированном программировании такое выполнение логики является формой совета .
В терминологии анализа потока управления высвобождение ресурсов должно постдоминировать над успешным приобретением ресурсов; [3] неспособность убедиться, что это ошибка, и путь кода, нарушающий это условие, приводит к утечке ресурсов. Утечки ресурсов часто представляют собой незначительные проблемы, обычно не приводящие к сбою программы, а вызывающие некоторое замедление работы программы или всей системы. [2] Однако они могут вызывать сбои – как самой программы, так и других программ – из-за исчерпания ресурсов: если в системе заканчиваются ресурсы, запросы на получение не выполняются. Это может представлять собой ошибку безопасности , если атака может привести к истощению ресурсов. Утечки ресурсов могут произойти при обычном выполнении программы (например, если просто забыть освободить ресурс) или только в исключительных обстоятельствах, например, когда ресурс не освобождается из-за исключения в другой части программы. Утечки ресурсов очень часто вызваны ранним выходом из подпрограммы или return
оператор или исключение, вызванное либо самой подпрограммой, либо более глубокой подпрограммой, которую она вызывает. Хотя высвобождение ресурсов из-за операторов возврата может быть обработано путем осторожного освобождения внутри подпрограммы перед возвратом, исключения не могут быть обработаны без каких-либо дополнительных языковых средств, которые гарантируют выполнение кода освобождения.
Более тонко: успешное получение ресурса должно доминировать над его освобождением, иначе код попытается освободить ресурс, который он не захватил. Последствия такого неправильного выпуска варьируются от молчаливого игнорирования до сбоя программы или непредсказуемого поведения. Эти ошибки обычно проявляются редко, поскольку для первого сбоя требуется выделение ресурсов, что обычно является исключительным случаем. Кроме того, последствия могут быть несерьезными, поскольку программа уже может давать сбой из-за невозможности получить необходимый ресурс. Однако это может помешать восстановлению после сбоя или превратить штатное завершение работы в неупорядоченное завершение работы. Это условие обычно обеспечивается путем первой проверки того, что ресурс был успешно получен перед его освобождением, либо путем наличия логической переменной для записи «успешно получено» - которой не хватает атомарности, если ресурс получен, но переменная флага не может быть обновлена, или наоборот. – или по дескриптору ресурса, являющегося типом, допускающим значение NULL. , где «null» означает «не удалось получено», что обеспечивает атомарность.
Конфликт за ресурсы [ править ]
![]() | Этот раздел нуждается в расширении . Вы можете помочь, добавив к нему . ( ноябрь 2016 г. ) |
Управление памятью [ править ]
Память можно рассматривать как ресурс, но управление памятью обычно рассматривается отдельно, главным образом потому, что выделение и освобождение памяти происходит значительно чаще, чем приобретение и освобождение других ресурсов, таких как дескрипторы файлов. Память, управляемая внешней системой, имеет сходство как с управлением (внутренней) памятью (поскольку это память), так и с управлением ресурсами (поскольку ею управляет внешняя система). Примеры включают память, управляемую через собственный код и используемую из Java (через собственный интерфейс Java ); и объекты в объектной модели документа (DOM), используемые из JavaScript . В обоих этих случаях диспетчер памяти ( сборщик мусора ) среды выполнения (виртуальной машины) не может управлять внешней памятью (нет управления общей памятью), и поэтому внешняя память рассматривается как ресурс и управляется аналогично. . Однако циклы между системами (JavaScript ссылается на DOM, ссылается обратно на JavaScript) могут затруднить или сделать невозможным управление.
управление и управление явное Лексическое
Ключевое различие в управлении ресурсами внутри программы заключается между лексическим управлением и явным управлением : можно ли обращаться с ресурсом как с лексической областью действия, например, со стековой переменной (время жизни ограничено одной лексической областью, получаемой при входе в нее или в определенной области и освобождается, когда выполнение выходит из этой области), или должен ли ресурс быть явно выделен и освобожден, например, ресурс, полученный внутри функции, а затем возвращенный из нее, который затем должен быть освобожден за пределами получающей функции. Лексическое управление, когда оно применимо, позволяет лучше разделить задачи и менее подвержено ошибкам.
Основные техники [ править ]
Основной подход к управлению ресурсами заключается в том, чтобы получить ресурс, что-то сделать с ним, а затем освободить его, получив код вида (показано открытием файла на Python):
f = open(filename)
...
f.close()
Это правильно, если промежуточное ...
код не содержит раннего выхода ( return
), язык не имеет исключений, и open
гарантированно добьется успеха. Однако это вызывает утечку ресурсов, если есть возврат или исключение, и приводит к неправильному освобождению неполученного ресурса, если open
может потерпеть неудачу.
Есть еще две фундаментальные проблемы: пара приобретение-релиз не является соседней (код выпуска должен быть написан далеко от кода приобретения), а управление ресурсами не инкапсулировано — программист должен вручную следить за тем, чтобы они всегда были в паре. В совокупности это означает, что получение и выпуск должны быть явно спарены, но не могут быть помещены вместе, что упрощает их неправильное спаривание.
Утечку ресурсов можно устранить на языках, поддерживающих finally
конструкцию (например, Python), помещая тело в try
пункт и выпуск в finally
пункт:
f = open(filename)
try:
...
finally:
f.close()
Это гарантирует правильный выпуск, даже если в теле есть возврат или выдано исключение. того, обратите внимание, что приобретение происходит до Кроме try
пункт, гарантирующий, что finally
предложение выполняется только в том случае, если open
код завершается успешно (без исключения), предполагая, что «отсутствие исключения» означает «успех» (как в случае с open
на Питоне). Если получение ресурса может завершиться неудачно без создания исключения, например, путем возврата формы null
, его также необходимо проверить перед выпуском, например:
f = open(filename)
try:
...
finally:
if f:
f.close()
Хотя это обеспечивает правильное управление ресурсами, оно не обеспечивает смежности или инкапсуляции. Во многих языках существуют механизмы, обеспечивающие инкапсуляцию, например with
заявление на Python:
with open(filename) as f:
...
Вышеуказанные приемы – защита от раскручивания ( finally
) и некоторая форма инкапсуляции — это наиболее распространенный подход к управлению ресурсами, встречающийся в различных формах в C#, Common Lisp , Java, Python, Ruby, Scheme и Smalltalk . [1] среди прочего; они датируются концом 1970-х годов на NIL- диалекте Лиспа; см. Обработка исключений § История . Вариаций реализации много, а также есть существенно разные подходы .
Подходы [ править ]
Защита от размотки [ править ]
Наиболее распространенным подходом к управлению ресурсами в разных языках является использование защиты от завершения, которая вызывается, когда выполнение выходит за пределы области действия — когда выполнение выходит за пределы блока, возвращается из блока или генерируется исключение. Это работает для ресурсов, управляемых стеком, и реализовано на многих языках, включая C#, Common Lisp, Java, Python, Ruby и Scheme. Основная проблема этого подхода заключается в том, что код выпуска (чаще всего в finally
пункт) может быть очень далек от кода получения (в нем отсутствует смежность ), и что код получения и выпуска всегда должен быть связан вызывающей стороной (в нем отсутствует инкапсуляция ). Их можно исправить либо функционально, используя замыкания/обратные вызовы/сопрограммы (Common Lisp, Ruby, Scheme), либо используя объект, который обрабатывает как получение, так и освобождение, и добавляя языковую конструкцию для вызова этих методов при входе и выходе элемента управления. область действия (C# using
, Ява try
-с-ресурсами, Python with
); см. ниже.
Альтернативный, более императивный подход — написать асинхронный код в прямом стиле : получить ресурс, а затем в следующей строке выполнить отложенный выпуск, который вызывается при выходе из области действия — синхронное получение с последующим асинхронным выпуском. Он возник в C++ как класс ScopeGuard, созданный Андреем Александреску и Петру Маржиненом в 2000 году.
[4] с улучшениями Джошуа Лерера, [5] и имеет прямую поддержку языка D через scope
ключевое слово ( ScopeGuardStatement ), где это один из подходов к безопасности исключений , в дополнение к RAII (см. ниже). [6] Он также был включен в Go, поскольку defer
заявление. [7] В этом подходе отсутствует инкапсуляция — необходимо явно сопоставить получение и выпуск — но позволяет избежать необходимости создавать объект для каждого ресурса (с точки зрения кода избегайте написания класса для каждого типа ресурса).
Объектно-ориентированное программирование [ править ]
В объектно-ориентированном программировании ресурсы инкапсулируются в объекты, которые их используют, например file
объект, имеющий поле , значением которого является дескриптор файла (или более общий дескриптор файла ). Это позволяет объекту использовать ресурс и управлять им без необходимости делать это пользователям объекта. Однако существует множество способов связи объектов и ресурсов.
Во-первых, возникает вопрос владения: есть ли у объекта ресурс?
- Объекты могут владеть ресурсами (через композицию объектов устанавливается сильная связь «имеет»).
- Объекты могут просматривать ресурсы (посредством агрегирования объектов , слабая связь «имеет»).
- Объекты могут взаимодействовать с другими объектами, имеющими ресурсы (через Association ).
Объекты, имеющие ресурс, могут приобретать и освобождать его по-разному, в разные моменты жизни объекта ; они встречаются парами, но на практике часто не используются симметрично (см. ниже):
- Получите/выпустите, пока объект действителен, с помощью методов (экземпляра), таких как
open
илиdispose
. - Получите/освободите во время создания/уничтожения объекта (в инициализаторе и финализаторе).
- Ни получить, ни освободить ресурс, вместо этого просто иметь представление или ссылку на ресурс, управляемый извне объекта, как при внедрении зависимостей ; конкретно, объект, который имеет ресурс (или может взаимодействовать с ресурсом), передается в качестве аргумента методу или конструктору.
Наиболее распространенным является получение ресурса во время создания объекта, а затем явное освобождение его с помощью метода экземпляра, обычно называемого dispose
. Это аналогично традиционному управлению файлами (получение во время open
, освободить явным образом close
), и известен как шаблон удаления . Это базовый подход, используемый в нескольких основных современных объектно-ориентированных языках, включая Java , C# и Python , и в этих языках имеются дополнительные конструкции для автоматизации управления ресурсами. Однако даже в этих языках более сложные объектные отношения приводят к более сложному управлению ресурсами, как описано ниже.
РАИИ [ править ]
Естественный подход состоит в том, чтобы сделать хранение ресурса инвариантом класса : ресурсы приобретаются во время создания объекта (в частности, инициализации) и освобождаются во время уничтожения объекта (в частности, финализации). Это известно как инициализация сбора ресурсов (RAII) и связывает управление ресурсами со временем жизни объекта , гарантируя, что живые объекты имеют все необходимые ресурсы. Другие подходы не делают хранение ресурса инвариантом класса, и, таким образом, объекты могут не иметь необходимых ресурсов (поскольку они еще не получены, уже выпущены или управляются извне), что приводит к ошибкам, таким как попытка чтения из закрытого файла. Этот подход связывает управление ресурсами с управлением памятью (в частности, управлением объектами), поэтому если нет утечек памяти (нет утечек объектов), то нет и утечек ресурсов . RAII естественным образом работает для ресурсов, управляемых кучей, а не только ресурсов, управляемых стеком, и является компонуемым: ресурсы, удерживаемые объектами в произвольно сложных отношениях (сложный objectgraph ) прозрачно высвобождаются просто путем уничтожения объекта (при условии, что это сделано правильно!).
RAII — это стандартный подход к управлению ресурсами в C++, но он мало используется за пределами C++, несмотря на свою привлекательность, поскольку он плохо работает с современным автоматическим управлением памятью, в частности с отслеживанием сборки мусора : RAII связывает управление ресурсами с управлением памятью, но они имеют существенные различия. . Во-первых, поскольку ресурсы дороги, их желательно освобождать как можно скорее, поэтому объекты, содержащие ресурсы, должны быть уничтожены, как только они станут мусором (перестанут использоваться). Уничтожение объектов происходит быстро при детерминированном управлении памятью, например, в C++ (объекты, выделенные в стеке, уничтожаются при раскручивании стека, объекты, выделенные в куче, уничтожаются вручную с помощью вызова delete
или автоматически с помощью unique_ptr
) или при детерминированном подсчете ссылок (когда объекты уничтожаются немедленно, когда их счетчик ссылок падает до 0), и, таким образом, RAII хорошо работает в этих ситуациях. Однако большинство современных методов автоматического управления памятью являются недетерминированными и не дают никаких гарантий того, что объекты будут уничтожены быстро или даже вообще! Это связано с тем, что дешевле оставить некоторый мусор выделенным, чем собирать каждый объект сразу же, как только он становится мусором. Во-вторых, высвобождение ресурсов при уничтожении объекта означает, что у объекта должен быть финализатор (в детерминированном управлении памятью известный как деструктор ) — объект нельзя просто освободить — что существенно усложняет и замедляет сбор мусора.
Сложные отношения [ править ]
Когда несколько объектов используют один ресурс, управление ресурсами может оказаться сложным.
Фундаментальный вопрос заключается в том, является ли отношение «имеет» отношением владения другим объектом ( композиция объекта ) или просмотром другого объекта ( агрегация объектов ). Распространенным случаем является объединение двух объектов в цепочку, как в шаблоне канала и фильтра , шаблоне делегирования , шаблоне декоратора или шаблоне адаптера . Если второй объект (который не используется напрямую) содержит ресурс, является ли первый объект (который используется напрямую) ответственным за управление ресурсом? На этот вопрос обычно отвечают одинаково, если первый объект владеет вторым объектом: если да, то объект-владелец также отвечает за управление ресурсами («наличие ресурса» является транзитивным ), а если нет, то это не так. Кроме того, один объект может «иметь» несколько других объектов, владеть одними и просматривать другие.
Оба случая часто встречаются, и соглашения различаются. Наличие объектов, которые косвенно используют ресурсы, несут ответственность за ресурс (композицию), обеспечивает инкапсуляцию (нужен только объект, который используют клиенты, без отдельных объектов для ресурсов), но приводит к значительной сложности, особенно когда ресурс используется несколькими объектами или объекты имеют сложные отношения. Если за ресурс отвечает только тот объект, который непосредственно использует ресурс (агрегация), связи между другими объектами, использующими ресурсы, можно игнорировать, но инкапсуляция отсутствует (кроме непосредственно использующего объекта): ресурс должен управляться напрямую, и может быть недоступен для косвенно использующего объекта (если он был выпущен отдельно).
С точки зрения реализации, в композиции объектов, если используется шаблон удаления, объект-владелец, таким образом, также будет иметь dispose
метод, который, в свою очередь, вызывает метод dispose
методы принадлежащих объектов, которые необходимо удалить; в RAII это обрабатывается автоматически (при условии, что принадлежащие объекты сами автоматически уничтожаются: в C++, если они являются значением или unique_ptr
, но не необработанный указатель: см. владение указателем ). При агрегации объектов просматривающему объекту ничего делать не нужно, так как он не несет ответственности за ресурс.
Оба часто встречаются. Например, в библиотеке классов Java Reader#close()
закрывает базовый поток, и их можно объединить в цепочку. Например, BufferedReader
может содержать InputStreamReader
, который, в свою очередь, содержит FileInputStream
и звоню close
на BufferedReader
в свою очередь закрывает InputStreamReader
, что, в свою очередь, закрывает FileInputStream
, что, в свою очередь, освобождает ресурс системного файла. Действительно, объект, который напрямую использует ресурс, может быть даже анонимным благодаря инкапсуляции:
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(fileName)))) {
// Use reader.
}
// reader is closed when the try-with-resources block is exited, which closes each of the contained objects in sequence.
Однако также можно управлять только тем объектом, который напрямую использует ресурс, и не использовать управление ресурсами для объектов-оболочек:
try (FileInputStream stream = new FileInputStream(fileName)))) {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
// Use reader.
}
// stream is closed when the try-with-resources block is exited.
// reader is no longer usable after stream is closed, but so long as it does not escape the block, this is not a problem.
Напротив, в Python csv.reader не владеет file
что читается, поэтому закрывать читалку не нужно (да и невозможно), а вместо этого file
сам должен быть закрыт. [8]
with open(filename) as f:
r = csv.reader(f)
# Use r.
# f is closed when the with-statement is exited, and can no longer be used.
# Nothing is done to r, but the underlying f is closed, so r cannot be used either.
В .NET соглашение заключается в том, чтобы ответственность возлагалась только на непосредственного пользователя ресурсов: «Вам следует реализовывать IDisposable только в том случае, если ваш тип напрямую использует неуправляемые ресурсы». [9]
В случае более сложного графа объектов , такого как несколько объектов, совместно использующих ресурс, или циклов между объектами, содержащими ресурсы, правильное управление ресурсами может быть довольно сложным, и возникают точно такие же проблемы, как и при финализации объекта (через деструкторы или финализаторы); например, может возникнуть проблема с истекшим прослушивателем и вызвать утечку ресурсов при использовании
шаблон наблюдателя (а наблюдатели владеют ресурсами). Существуют различные механизмы, позволяющие лучше контролировать управление ресурсами. Например, в Google Closure библиотеке goog.Disposable
класс предоставляет registerDisposable
метод для регистрации других объектов, которые будут удалены с помощью этого объекта, вместе с различными методами экземпляра и класса более низкого уровня для управления удалением.
Структурное программирование [ править ]
В структурированном программировании управление ресурсами стека осуществляется просто путем вложения кода, достаточного для обработки всех случаев. Это требует только одного возврата в конце кода и может привести к сильно вложенному коду, если необходимо получить много ресурсов, что считают антишаблоном некоторые — Arrow Anti Pattern , [10] из-за треугольной формы от последовательного вложения.
Пункт об очистке [ править ]
Еще один подход, который допускает ранний возврат, но объединяет очистку в одном месте, состоит в том, чтобы иметь один возврат функции, которому предшествует код очистки, и использовать goto для перехода к очистке перед выходом. Это нечасто встречается в современном коде, но встречается в некоторых случаях использования C.
См. также [ править ]
Ссылки [ править ]
- ^ Jump up to: Перейти обратно: а б Бек 1997 , стр. 37–39.
- ^ Jump up to: Перейти обратно: а б Элдер, Джексон и Либлит, 2008 , с. 3.
- ^ Элдер, Джексон и Либлит 2008 , с. 2.
- ^ « Общий: измените способ написания безопасного к исключениям кода - навсегда », Андрей Александреску и Петру Маржинян, 1 декабря 2000 г., Dr. Dobb's
- ^ ScopeGuard 2.0 , Джошуа Лерер
- ^ D: Безопасность исключений
- ↑ Отсрочка, паника и восстановление , Эндрю Джерранд, The Go Blog, 4 августа 2010 г.
- ^ Python: Нет csv.close()?
- ^ «Одноразовый интерфейс» . Проверено 3 апреля 2016 г.
- ^ Код выравнивания стрелок , Джефф Этвуд, 10 января 2006 г.
- Бек, Кент (1997). Шаблоны лучших практик Smalltalk . Прентис Холл. ISBN 978-0134769042 .
- Старейшина, Мэтт; Джексон, Стив; Либлит, Бен (октябрь 2008 г.). Сэндвичи с кодом (PDF) (Технический отчет). Университет Висконсина-Мэдисона . 1647 г., аннотация
{{cite tech report}}
: Внешняя ссылка в
( помощь ) CS1 maint: постскриптум ( ссылка )|postscript=