Что такое handle в программировании
Handle Класс
Определение
Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
Представляет тип, время существования которого управляется средой выполнения рабочего процесса.
Комментарии
Среда выполнения рабочего процесса управляет временем существования типов, производных от Handle. Для переменных рабочего процесса, использующих типы обработки, среда выполнения использует необходимый конструктор без параметров для создания экземпляра типа, присваивает его Variable и вызывает OnInitialize при инициализации среды. Когда переменная выходит за пределы области, вызывается метод OnUninitialize.
Конструкторы
Инициализирует новый экземпляр класса Handle.
Свойства
Возвращает имя, используемое этим дескриптором Handle при добавлении к свойствам выполнения действия.
Возвращает экземпляр ActivityInstance, содержащий переменную Variable, которая содержит дескриптор Handle.
Методы
Определяет, равен ли указанный объект текущему объекту.
Служит хэш-функцией по умолчанию.
Возвращает объект Type для текущего экземпляра.
Создает неполную копию текущего объекта Object.
Вызывается средой выполнения рабочего процесса, когда инициализируется среда, содержащая Handle.
Вызывается средой выполнения рабочего процесса, когда переменная Variable для Handle выходит за пределы области.
Вызывает исключение InvalidOperationException, если дескриптор Handle не инициализирован.
Возвращает строку, представляющую текущий объект.
Использование handle и intrusive reference counter-ов в многопоточных средах в языке C
Доступ к одим и тем же данным в нескольких потоках считается плохой практикой, но во многих случаях это неизбежно, и это не тот вопрос, который обсуждается здесь. Вопрос который здесь обсуждается, это как организовать такой доступ наиболее безопасным способом. Также тут не обсуждаются атомарные операции, которые тут упоминаются: разные компиляторы предлагают различные средства для таких операций.
В многопоточной среде при использовании объекта или структуры данных, один из главных вопросов, помимо прочего, это гарантия того, что объект к которому производится доступ все еще жив и память, выделенная для структуры не освобождена.
Это может быть сделано несколькими способами, но мы будем говорить только о двух из них: хэндлы (handles) и встроенные счётчики ссылок (intrusive reference counters).
Хэндлы — это небольшие структуры, которые содержат указатель на объект данных и вспомогательные данные, чтобы гарантировать, что объект еще жив. Как правило для работы с хэндлами пишутся две функции: lock_handle и unlock_handle (имена выбраны произвольно, чтобы показать функциональность). Lock_handle проверяет «живость» объекта, увеличивает атомарный счетчик ссылок и возвращает указатель на объект данных, если он ещё доступен. Если нет, то функция возвращает NULL, или с помощью другого способа даёт знать, что объект больше не доступен. Соответственно своему названию, unlock_handle атомарно уменьшает счетчик ссылок и как только он достигает значения 0, удаляет объект.
Встроенные счетчики ссылок — это атомарные числовые переменные внутри объекта данных, которые считают количество ссылок в программе на указанный объект данных. Как только счетчик ссылок достигает значения 0, объект удаляется.
Теперь давайте взглянем на преимущества и распространенные ошибки обеих стратегий и определим, какую из них лучше использовать в конкретных случаях. Сперва давайте разберемся с встроенными счетчиками ссылок.
Встроенные счетчики ссылок
Встроенные счетчики ссылок, как понятно из названия, должны быть встроены в структуру данных: это атомарное целое число. В простой структуре, назовем ее data_packet_s, подобная модификация может выглядеть так:
Именно это является главным недостатком этого подхода: он нуждается в модификации структуры данных, поэтому использовать его можно только для таких структур, которые мы можем изменить. Мы не можем использовать его с произвольным типом данных или структурами, например, библиотечными.
Любопытно, но это же факт является и преимуществом. Преимущество состоит в том, что нам не нужны никакие дополнительные структуры и дополнительное выделение памяти для подобных структур.
Еще один недостаток, или, скорее, специфика этого подхода заключается в следующем: доступность объекта должна быть гарантирована, когда происходит приращение счетчика ссылок. Другими словами, мы не можем просто хранить указатель на объект и дожидаться момента, когда мы хотим получить к нему доступ, и затем просто увеличить счетчик ссылок и далее работать с объектом, поскольку между моментом когда мы сохранили указатель и моментом когда мы начинаем использовать объект он может быть уничтожен, при этом уничтожается и счётчик ссылок. Давайте продемонстрируем простой случай такого события, используя простой условный язык.
В указанном случае, чтобы обеспечить целостность объекта, придется увеличить количество ссылок в потоке 1 и передать полученную ссылку в собственность потоку 2 (уменьшать reference_count будет поток 2 после того как объект больше не нужен). Другими словами, эта схема может работать очень хорошо, если необходимо обработать объект в другом потоке, и забыть о нем. Это может быть продемонстрировано с помощью следующей иллюстрации:
Такая схема может быть использована любым количеством потоков при условии, что каждый поток, который увеличивает счетчик ссылок, уже владеет по крайней мере одним экземпляром-ссылкой (либо увеличил количество ссылок и не передал собственность, либо получил экземпляр-ссылку в собственность от другого потока). Таким образом, в последнем случае поток 1 может увеличить счетчик ссылок несколько раз (по одному для каждого потока) и лишь после этого он может запустить несколько потоков (столько, на сколько был увеличен счетчик ссылок.
Тут становится виден и второй недостаток: мы увеличивать и уменьшаем счетчик ссылок в разных потоках, что часто может приводить к различным ошибкам, начиная от утечек памяти когда ссылка не освобождается, заканчивая двойным освобождением ссылки и соответственно ложным освобождением объекта, что ведёт к его удалению во время того как он ещё используется в другом потоке или потоках.
Эта схема реализует так называемый «shared ownership», когда потоки совместно владеют объектом, и нить, уменьшающая счетчик ссылок до нуля, будет удалять объект.
И если вы хотите сохранить указатель на объект в одном из потоков, и использовать его при необходимости вы увидите, что память выделенная вашей программой будет расти с каждым хранящимся объектом, потому что поток никогда не освободит ссылку на объект, «живость» которого он хочет гарантировать.
Другая возможная ошибка в таком подходе это обращение к косвенно переданным объектам, то есть объектам, которые передаются по указателю, содержащемуся в одном из других, прямо или косвенно переданных объектов, которые ссылаются, но не владеют объектом. Все такие объекты должны быть известны, при этом должен быть обеспечен безопасный доступ ко всем подобным объектам, если они используются наряду с основным.
Итак, давайте просуммируем недостатки и преимущества встроенных счетчиков ссылок:
Хэндлы
Хэндлы — это легкие структуры, которые передаются по значению, они ссылаются на объект, управляют ссылками и обеспечивают целостность объекта.
Очевидно, что хэндлы, которые ссылаются на один и тот же объект, будут иметь указатель на один счетчик ссылок. Как правило, это означает, что счетчик ссылок должен быть выделен на куче динамически. Простейшая структура хэндла, которая первой приходит на ум, может выглядеть так:
Где reference_count выделяется при создании первого хэндла на данный объект.
Первый недостаток такого подхода уже очевиден. Мы должны управлять еще одной дополнительной структурой, выделять еще одну область памяти для счетчика ссылок. Но это окупается, если вы хотите использовать подсчет ссылок со структурами, к которым вы не имеете доступа.
Типичное применение такого хэндла будет следующим:
Давайте посмотрим, что происходит, когда последняя ссылка на объект удалена. Прежде всего, мы, очевидно, хотим, чтобы удалился управляемый объект. Если объект является простым куском выделенной памяти, мы просто хотим, чтобы память, используемая объектом была освобождена. Но в большинстве случаев это не так. Как и в вышеупомянутом примере data_packet_s, где мы хотели бы освободить также и память data_buffer. Если мы используем хэндл для только одного типа объектов, это не создаёт большой проблемы. Но если мы хотим, чтобы хэндл мог обрабатывать разыличные типы, это привносит ещё один вопрос: как правильно удалить управляемый объект?
Мы можем добавить в хэндл ещё одно поле: указатель на функцию, которая будет использоваться для уничтожения/освобождения управляемого объекта. Теперь хэндл выглядит следующим образом:
Теперь release_handle не обязательно знать специфику объекта, чтобы удалить его, он просто будет использовать функцию, которую мы сохранили в хэндле, так что давайте вернёмся к тому, что происходит, когда последняя ссылка будет освобождена.
После этого мы хотели бы освободить память, которая содержит reference_count. Но не тут то было: сделать это будет ужасной ошибкой. Если другие хэндлы на этот же объект до сих пор хранятся в других потоках, после освобождения памяти они будут ссылаться на reference_count, который уже удален. А на следующей попытке получить доступ к объекту, мы будем иметь попытку обращения к освобожденной памяти.
Есть ли решение, которое позволило бы не допустить утечки памяти но при этом избежать обращений к освобождённой памяти счетчиков ссылок? Такой способ есть. Это пул объектов, который будут управлять освобожденными счетчиками. И вот тут-то и появляется проблема, найти которую может быть довольно непросто, и которая известна как «проблема ABA». Представьте себе, ситуацию, когда у вас есть хэндл на объект. Один из потоков удаляет объект. Затем конструируется другой объект, и для него создается управляющих хэндл. Что при этом произойдет?
Когда объект уничтожен, reference_count связанный с данным объектом (назовем его object1) освобождается обратно в пул объектов со значением 0. Пока что всё идёт по плану. Но когда выделяется другой хэндл для нового объекта (назовем его object2), то reference_count, который будет связан с этим объектом берется из пула объектов, при этом данный reference_count устанавливается в 1. Теперь представьте, что поток хранящий хэндл на object1 пытается получить указатель. Это удастся, потому что reference_count на который указывает данный хэндл уже не 0, хотя он и принадлежит теперь object2. Функция блокировки вернет неверный указатель, программа (если повезёт) потерпит крушение, или (если не повезёт) повредит содержимое памяти обращением к освобожденному участку.
Решение, разумеется, существует, иначе я бы не писал всё это.
Мы хотим сделать структуру handle_s настолько легкой, насколько это возможно, чтобы иметь возможность передать ее по значению, а не по указателю, так что сделаем следующее: создадим две структуры, одна из которых будет «слабым» хэндлом, то есть не ограничивать и не проверять объект, которым она управляет, а другой будет «сильным» хэндлом, т.е. таким, который будет иметь жесткую связь с конкретным управляемым объектом и вернет NULL, если «слабый» хэндл связанный с ним больше не ссылается на тот же объект.
Давайте определим их так:
Итак, как вы видите, теперь обе ручки имеют поле «version», и strong_handle_s уже не имеет указателя на объект, так как он теперь хранится в общем для объекта weak_handle_s.
Давайте посмотрим, как он защищает нас от проблемы ABA, показанной выше.
В strong_handle_s и weak_handle_s, которые ссылаются на один и тот же объект имеются поля «version», которые равны друг другу.
Всякий раз, когда ручка освобождается и weak_handle_s помещается обратно в пул объектов, мы будем увеличивать номер версии в weak_handle_s.
В следующий раз, если освобожденный в пул weak_handle_s переиспользуется для обработки другого объекта, он будет иметь номер версии, отличный от номера версии который был у объекта, который был освобожден. Теперь в функции lock_handle, сравнивая поля версии в обоих, «слабом» и «сильном» хэндле, мы можем сказать, ссылается ли weak_handle_s по-прежнему на тот объект, указатель которого мы пытаемся получить из strong_handle_s, и вернуть NULL, если это не так.
Так что, как мы видим, хэндлы приносят некоторые довольно сложные проблемы, но он также имеет и свои плюсы: хэндл может быть сохранен и забыт, пока нам не понадобится управляемый им объект; хэндл не является встроенным, что означает, что мы можем использовать его практически с любыми типами данных.
Кроме того, передача хэндла не обязательно означает передачу права собственности на объект, так что он может быть использован вместо указателя в качестве безопасной ссылки на объекты везде, где они не являются собственностью ссылающегося объекта (в структурах данных и т.д.).
Таким образом хэндлами сложнее управлять. Но и возможности их гораздо мощнее.
Выводы
Встроенный счетчик ссылок реализует общие сильные ссылки (strong reference), которые должны удерживать счетчик ссылок, пока объект не будет гарантированно невостребован. Пока все ссылки не будут разлочены, объект не будет удален. Если ссылка разблокирована в потоке, который имел в собственности лишь одну ссылку, этот поток уже больше не сможет гарантированно безопасно получить еще одну ссылку.
Хэндл реализует общую слабую ссылку (weak reference). Если объект жив, функция lock_handle вернет указатель на запрашиваемый объект, и объект гарантированно не будет удален, пока хэндл не будет разлочен. Это безопасно для блокировки и разблокировки сколько угодно раз, так как хэндл является отдельным объектом, и гарантированно является валидным участком памяти. Соответствующие меры должны быть приняты, чтобы гарантировать, что разделяемая память счетчика ссылок не освобождается, когда объект достигает счетчика ссылок 0, и что повторно используемый счетчик ссылок не используется, чтобы проверить количество ссылок на уже освобожденные объекты.
Что такое дескриптор в C++?
Мне сказали, что дескриптор-это своего рода указатель, но нет, и что он позволяет вам сохранять ссылку на объект, а не сам объект. Что такое более подробное объяснение?
7 ответов:
дескриптор может быть любым от целочисленного индекса до указателя на ресурс в пространстве ядра. Идея заключается в том, что они обеспечивают абстракцию ресурса, поэтому вам не нужно много знать о самом ресурсе, чтобы использовать его.
например, HWND в Win32 API является дескриптором для окна. Само по себе это бесполезно: вы не можете извлечь из него никакой информации. Но передайте его в правильные функции API, и вы можете выполнять с ним множество различных трюков. Внутренне вы можете подумайте о HWND как просто индекс в таблице графического интерфейса windows (что может быть не обязательно так, как это реализовано, но это имеет смысл).
EDIT: не на 100% уверен, что конкретно вы задавали в своем вопросе. В основном речь идет о чистом C/C++.
дескриптор-это указатель или индекс, к которому не прикреплен видимый тип. Обычно вы видите что-то вроде:
поэтому в вашем коде вы просто передаете дескриптор как непрозрачное значение.
в коде, который использует объект, он приводит указатель к реальному типу структуры и использует его:
или он использует его в качестве индекса для массива / вектора:
дескриптор и своего рода указатель в том, что это обычно способ ссылки на некоторую сущность.
точнее было бы сказать, что указатель-это один тип дескриптора, но не все дескрипторы являются указателями.
например, дескриптор может также быть некоторым индексом в таблице в памяти, которая соответствует записи, которая сама содержит указатель на некоторый объект.
главное, что когда у вас есть «ручка», вы не знаете и не заботитесь о том, как эта ручка на самом деле заканчивается идентификацией того, что она идентифицирует, все, что вам нужно знать, это то, что она делает.
также должно быть очевидно, что нет единого ответа на вопрос «что именно является ручкой», потому что ручки для разных вещей, даже в одной и той же системе, могут быть реализованы по-разному «Под капотом». Но вам не нужно беспокоиться об этих различиях.
В C++/CLI дескриптор-это указатель на объект, расположенный в куче GC. Создание объекта в (неуправляемой) куче C++ достигается с помощью new и результат a new выражение является» нормальным » указателем. Управляемый объект выделяется в куче GC (managed) с помощью gcnew выражение. Результатом будет дескриптор. Вы не можете сделать арифметику указателя на дескрипторах. Вы не освобождаете ручки. О них позаботится ГК. Кроме того, GC может свободно перемещать объекты в управляемой куче и обновите маркеры, чтобы они указывали на новые местоположения во время работы программы.
Это появляется в контексте Handle-Body-Idiom, также называемые идиомы Pimpl. Это позволяет сохранить ABI (двоичный интерфейс) библиотеки тем же самым, сохраняя фактические данные в другой объект класса, на который просто ссылается указатель, удерживаемый в объекте «дескриптор», состоящий из функций, которые делегируют этому классу «тело».
также полезно включить постоянное время и исключение безопасной замены двух объектов. Для этого просто указатель, указывающий на объект тела должен быть заменен.
HANDLE-это тип, определенный в winnt.H файл в visual studio (windows).
Я.с: typedef void *HANDLE;
дескриптор будет таким, каким вы хотите его видеть.
дескриптор может быть целым числом без знака, используемым в некоторой таблице подстановки.
дескриптор может быть указателем на больший набор данных или в него.
Это зависит от того, как ведет себя код, который использует дескриптор. Это определяет тип дескриптора.
Почему термин ‘дескриптор ‘ используется то, что важно. Это указывает на них как на тип идентификации или доступа к объекту. В смысле, к программист, они представляют собой ‘ключ’ или доступ к чему-то.
Руководство к дескрипторам
Краткий обзор
В этой статье я расскажу о том, что такое дескрипторы, о протоколе дескрипторов, покажу как вызываются дескрипторы. Опишу создание собственных и исследую несколько встроенных дескрипторов, включая функции, свойства, статические методы и методы класса. С помощью простого приложения покажу, как работает каждый из них, приведу эквиваленты внутренней реализации работы дескрипторов кодом на чистом питоне.
Изучение того, как работают дескрипторы, откроет доступ к большему числу рабочих инструментов, поможет лучше понять как работает питон, и ощутить элегантность его дизайна.
Введение и определения
Протокол дескрипторов
Собственно это всё. Определите любой из этих методов и объект будет считаться дескриптором, и сможет переопределять стандартное поведение, если его будут искать как атрибут.
Дескрипторы данных и не данных отличаются в том, как будет изменено поведение поиска, если в словаре объекта уже есть запись с таким же именем как у дескриптора. Если попадается дескриптор данных, то он вызывается раньше, чем запись из словаря объекта. Если в такой же ситуации окажется дескриптор не данных, то запись из словаря объекта имеет преимущество перед этим дескриптором.
Вызов дескрипторов
Пример дескриптора
Этот простой протокол предоставляет просто увлекательные возможности. Некоторые из них настолько часто используются, что были объединены в отдельные функции. Свойства, связанные и несвязанные методы, статические методы и методы класса — все они основаны на этом протоколе.
Свойства
Вызова property() достаточно, чтобы создать дескриптор данных, который вызывает нужные функции во время доступа к атрибуту. Вот его сигнатура:
В документации показано типичное использование property() для создания управляемого атрибута x :
Вот эквивалент property на чистом питоне, чтобы было понятно как реализовано property() с помощью протокола дескрипторов:
Встроенная реализация property() может помочь, когда существовал интерфейс доступа к атрибуту и произошли какие-то изменения, в результате которых понадобилось вмешательство метода.
Функции и методы
В питоне все объектно-ориентированные возможности реализованы с помощью функционального подхода. Это сделано совсем незаметно с помощью дескрипторов не данных.
С помощью интерпретатора мы можем увидеть как на самом деле работает дескриптор функции:
Вывод интерпретатора подсказывает нам, что связанные и несвязанные методы — это два разных типа. Даже если они могли бы быть реализованы таким образом, на самом деле, реализация PyMethod_Type в файле Objects/classobject.c содержит единственный объект с двумя различными отображениями, которые зависят только от того, есть ли в поле im_self значение или там содержится NULL (C эквивалент значения None ).
Статические методы и методы класса
Дескрипторы не данных предоставляют простой механизм для различных вариантов привязки функций к методам.
Так как staticmethod() возвращает функцию без изменений, то этот пример не удивляет:
Если использовать протокол дескриптора не данных, то на чистом питоне staticmethod() выглядел бы так:
В отличие от статических методов, методы класса подставляют в начало вызова функции ссылку на класс. Формат вызова всегда один и тот же, и не зависит от того, вызываем мы метод через объект или через класс.
Это поведение удобно, когда нашей функции всегда нужна ссылка на класс и ей не нужны данные. Один из способов использования classmethod() — это создание альтернативных конструкторов класса. В питоне 2.3, метод класса dict.fromkeys() создаёт новый словарь из списка ключей. Эквивалент на чистом питоне будет таким:
Теперь новый словарь уникальных ключей можно создать таким образом:
Если использовать протокол дескриптора не данных, то на чистом питоне classmethod() выглядел бы так:
Handle In C++
Программа Handle — это инструмент, который выводит сведения об открытых дескрипторах для любого процесса в системе. Она позволяет посмотреть, какие программы открыли файл, а также увидеть тип объектов и имена всех дескрипторов программы.
Использование
Программа Handle предназначена для поиска ссылок на открытые файлы, поэтому, если никакие параметры командной строки не заданы, она выводит список значений всех дескрипторов в системе, которые ссылаются на открытые файлы, с указанием имен этих файлов. Изменить работу программы можно с помощью нескольких параметров.
Использование: handle [[-a] [-u] | [-c [-y]] | [-s]] [-p |
-a
Вывод сведений обо всех типах дескрипторов, а не только о тех из них, которые ссылаются на файлы. К другим типам дескрипторов относятся порты, разделы реестра, элементы синхронизации, потоки и процессы.
ВНИМАНИЕ! Закрытие дескрипторов может привести к нестабильной работе приложений или системы.
-y
Не запрашивать подтверждение закрытия дескриптора.
-s
Выводить число открытых дескрипторов каждого типа.
-u
При поиске дескрипторов показывать имя пользователя-владельца.
показывает открытые файлы для всех процессов, имя которых начинается с «exp», например для процесса Explorer.
имя
Этот параметр позволяет настроить программу Handle на поиск ссылок на объект с указанным именем. Например, если требуется узнать, какой процесс (если таковой имеется) открыл каталог «c:\windows\system32», это можно сделать с помощью следующей команды:
При поиске соответствия имени регистр букв не учитывается, а указанный в качестве параметра фрагмент имени может представлять собой любую часть интересующих вас путей.