Anotar comportamiento de bloqueo

Para evitar errores de simultaneidad en el programa multiproceso, siga siempre un método de bloqueo adecuada y use anotaciones SAL.

Los errores de simultaneidad son especialmente difíciles de reproducir, diagnosticar y depurar porque no son deterministas. El razonamiento sobre la intercalación de subprocesos es difícil en el mejor de los casos y resulta poco práctico al diseñar un cuerpo de código que tenga más de unos cuantos subprocesos. Por lo tanto, es recomendable seguir un método de bloqueo en los programas multiproceso. Por ejemplo, obedecer un orden de bloqueo mientras adquiere varios bloqueos ayuda a evitar interbloqueos, y adquirir el bloqueo de protección adecuado antes de acceder a un recurso compartido ayuda a evitar condiciones de carrera.

Por desgracia, las reglas de bloqueo aparentemente sencillas pueden ser sorprendentemente difíciles de seguir en la práctica. Una limitación fundamental en los lenguajes de programación y los compiladores actuales es que no admiten directamente la especificación y el análisis de los requisitos de simultaneidad. Los programadores tienen que confiar en comentarios informales de código para expresar sus intenciones sobre cómo usan los bloqueos.

Las anotaciones SAL de simultaneidad están diseñadas para ayudarle a especificar los efectos secundarios del bloqueo, la responsabilidad del bloqueo, la protección de datos, la jerarquía de orden de bloqueo y otro comportamiento de bloqueo esperado. Al hacer explícitas reglas implícitas, las anotaciones de simultaneidad SAL proporcionan una manera coherente de documentar cómo el código usa reglas de bloqueo. Las anotaciones de simultaneidad también mejoran la capacidad de las herramientas de análisis de código para encontrar condiciones de carrera, interbloqueos, operaciones de sincronización no coincidentes y otros errores sutiles de simultaneidad.

Instrucciones generales

Mediante las anotaciones, puede indicar los contratos implícitos en las definiciones de función entre implementaciones (destinatarios) y clientes (autores de la llamada). También puede expresar invariables y otras propiedades del programa que pueden mejorar aún más el análisis.

SAL admite muchos tipos diferentes de primitivos de bloqueo, por ejemplo, secciones críticas, exclusiones mutuas, bloqueos por subproceso y otros objetos de recursos. Muchas anotaciones de simultaneidad toman una expresión de bloqueo como parámetro. Por convención, la expresión de ruta de acceso del objeto de bloqueo subyacente indica un bloqueo.

Algunas reglas de propiedad del subproceso que se deben tener en cuenta:

  • Los bloqueos por subproceso son bloqueos sin contar que tienen una propiedad de subproceso clara.

  • Las exclusión mutuas y las secciones críticas son bloqueos contados que tienen una propiedad clara del subproceso.

  • Los semáforos y los eventos son bloqueos contados que no tienen propiedad clara del subproceso.

Anotaciones de bloqueo

En la tabla siguiente se enumeran las anotaciones de bloqueo.

Annotation Descripción
_Acquires_exclusive_lock_(expr) Anota una función e indica que, en estado posterior, la función incrementa en uno el recuento de bloqueos exclusivos del objeto de bloqueo designado por expr.
_Acquires_lock_(expr) Anota una función e indica que, en estado posterior, la función incrementa en uno el recuento de bloqueos del objeto de bloqueo designado por expr.
_Acquires_nonreentrant_lock_(expr) Se adquiere el bloqueo designado por expr. Se notifica un error si el bloqueo ya se mantiene.
_Acquires_shared_lock_(expr) Anota una función e indica que, en estado posterior, la función incrementa en uno el recuento de bloqueos compartidos del objeto de bloqueo designado por expr.
_Create_lock_level_(name) Instrucción que declara el símbolo name como un nivel de bloqueo para que se pueda usar en las anotaciones _Has_Lock_level_ y _Lock_level_order_.
_Has_lock_kind_(kind) Anota cualquier objeto para refinar la información de tipo de un objeto de recurso. En ocasiones, se usa un tipo común para diferentes tipos de recursos y el tipo sobrecargado no es suficiente para distinguir los requisitos semánticos entre varios recursos. Esta es una lista de parámetros kind predefinidos:

_Lock_kind_mutex_
Identificador de tipo de bloqueo para exclusión mutuas.

_Lock_kind_event_
Identificador de tipo de bloqueo para eventos.

_Lock_kind_semaphore_
Identificador de tipo de bloqueo para semáforos.

_Lock_kind_spin_lock_
Identificador de tipo de bloqueo para bloqueos por subproceso.

_Lock_kind_critical_section_
Identificador de tipo de bloqueo para secciones críticas.
_Has_lock_level_(name) Anota un objeto de bloqueo y le proporciona el nivel de bloqueo de name.
_Lock_level_order_(name1, name2) Instrucción que proporciona el orden de bloqueo entre name1 y name2. Los bloqueos que tienen el nivel name1 deben adquirirse antes de los bloqueos que tienen el nivel name2.
_Post_same_lock_(expr1, expr2) Anota una función e indica que, en un estado posterior los dos bloqueos, expr1 y expr2, se tratan como si fueran el mismo objeto de bloqueo.
_Releases_exclusive_lock_(expr) Anota una función e indica que, en estado posterior, la función disminuye en uno el recuento de bloqueos exclusivos del objeto de bloqueo designado por expr.
_Releases_lock_(expr) Anota una función e indica que, en estado posterior, la función disminuye en uno el recuento de bloqueos del objeto de bloqueo designado por expr.
_Releases_nonreentrant_lock_(expr) El bloqueo designado por expr se libera. Se notifica un error si el bloqueo no se mantiene actualmente.
_Releases_shared_lock_(expr) Anota una función e indica que, en estado posterior, la función disminuye en uno el recuento de bloqueos compartidos del objeto de bloqueo designado por expr.
_Requires_lock_held_(expr) Anota una función e indica que, en estado previo, el recuento de bloqueos del objeto designado por expr es al menos uno.
_Requires_lock_not_held_(expr) Anota una función e indica que, en estado previo, el recuento de bloqueos del objeto designado por expr es cero.
_Requires_no_locks_held_ Anota una función e indica que los recuentos de bloqueos de todos los bloqueos conocidos para el comprobador son cero.
_Requires_shared_lock_held_(expr) Anota una función e indica que, en estado previo, el recuento de bloqueos compartidos del objeto designado por expr es al menos uno.
_Requires_exclusive_lock_held_(expr) Anota una función e indica que, en estado previo, el recuento de bloqueos exclusivos del objeto designado por expr es al menos uno.

Intrínsecos de SAL para objetos de bloqueo no expuestos

La implementación de las funciones de bloqueo asociadas no expone ciertos objetos de bloqueo. En la tabla siguiente se enumeran las variables intrínsecas SAL que permiten notaciones en funciones que operan en esos objetos de bloqueo no expuestos.

Annotation Descripción
_Global_cancel_spin_lock_ Describe el bloqueo por subproceso de cancelación.
_Global_critical_region_ Describe la región crítica.
_Global_interlock_ Describe las operaciones interbloqueadas.
_Global_priority_region_ Describe la región de prioridad.

Anotaciones de acceso a datos compartidos

En la tabla siguiente se enumeran las anotaciones para el acceso a datos compartidos.

Annotation Descripción
_Guarded_by_(expr) Anota una variable e indica que, cada vez que se accede a la variable, el recuento de bloqueos del objeto de bloqueo designado por expr es al menos uno.
_Interlocked_ Anota una variable y es equivalente a _Guarded_by_(_Global_interlock_).
_Interlocked_operand_ El parámetro de función anotada es el operando de destino de una de las distintas funciones Interlocked. Esos operandos deben tener propiedades adicionales específicas.
_Write_guarded_by_(expr) Anota una variable e indica que, cada vez que se modifica la variable, el recuento de bloqueos del objeto de bloqueo designado por expr es al menos uno.

Anotaciones de bloqueo inteligente y RAII

Los bloqueos inteligentes suelen encapsular bloqueos nativos y administrar su duración. En la tabla siguiente se enumeran las anotaciones que se pueden usar con bloqueos inteligentes y patrones de codificación RAII con compatibilidad con la semántica move.

Annotation Descripción
_Analysis_assume_smart_lock_acquired_(lock) Indica al analizador que suponga que se ha adquirido un bloqueo inteligente. Esta anotación espera un tipo de bloqueo de referencia como parámetro.
_Analysis_assume_smart_lock_released_(lock) Indica al analizador que suponga que se ha liberado un bloqueo inteligente. Esta anotación espera un tipo de bloqueo de referencia como parámetro.
_Moves_lock_(target, source) Describe una operación move constructor, que transfiere el estado de bloqueo del objeto source al objeto target. El objeto target se considera un objeto recién construido, por lo que cualquier estado que tenía antes se pierde y se reemplaza por el estado source. El objeto source también se restablece a un estado limpio sin recuentos de bloqueos ni destino de alias, pero los alias que apuntan a él permanecen sin cambios.
_Replaces_lock_(target, source) Describe la semántica move assignment operator en la que se libera el bloqueo de destino antes de transferir el estado del origen. Puede considerarlo como una combinación de _Moves_lock_(target, source) precedida por _Releases_lock_(target).
_Swaps_locks_(left, right) Describe el comportamiento swap estándar, que da por hecho que los objetos left y right intercambian su estado. El estado intercambiado incluye el recuento de bloqueos y el destino de alias, si existe. Los alias que apuntan a los objetos left y right permanecen sin cambios.
_Detaches_lock_(detached, lock) Describe un escenario en el que un tipo de contenedor de bloqueos permite la desasociación con su recurso contenido. Es parecido a cómo funciona std::unique_ptr con su puntero interno: permite a los programadores extraer el puntero y dejar su contenedor de puntero inteligente en un estado limpio. std::unique_lock admite una lógica parecida y se puede implementar en contenedores de bloqueo personalizados. El bloqueo desasociado conserva su estado (número de bloqueos y destino de alias, si existe), mientras que el contenedor se restablece para que contenga un recuento de bloqueos cero y ningún destino de alias, al tiempo que conserva sus propios alias. No hay ninguna operación en los recuentos de bloqueos (liberar y adquirir). Esta anotación se comporta exactamente como _Moves_lock_, salvo que el argumento desasociado debe ser return en lugar de this.

Consulte también