stringtranslate.com

Lock (informática)

En informática , un bloqueo o mutex (de exclusión mutua ) es una primitiva de sincronización que evita que varios subprocesos de ejecución modifiquen el estado o accedan a él a la vez. Los bloqueos imponen políticas de control de concurrencia de exclusión mutua y, con una variedad de métodos posibles, existen múltiples implementaciones únicas para diferentes aplicaciones.

Tipos

Generalmente, los bloqueos son bloqueos de asesoramiento , donde cada subproceso coopera adquiriendo el bloqueo antes de acceder a los datos correspondientes. Algunos sistemas también implementan bloqueos obligatorios , donde intentar acceder no autorizado a un recurso bloqueado forzará una excepción en la entidad que intenta realizar el acceso.

El tipo de bloqueo más simple es un semáforo binario . Proporciona acceso exclusivo a los datos bloqueados. Otros esquemas también brindan acceso compartido para leer datos. Otros modos de acceso ampliamente implementados son el exclusivo, el de intención de exclusión y el de intención de actualización.

Otra forma de clasificar los bloqueos es según lo que sucede cuando la estrategia de bloqueo impide el progreso de un hilo. La mayoría de los diseños de bloqueo bloquean la ejecución del subproceso que solicita el bloqueo hasta que se le permite acceder al recurso bloqueado. Con un spinlock , el hilo simplemente espera ("gira") hasta que el bloqueo esté disponible. Esto es eficaz si los subprocesos se bloquean durante un breve periodo de tiempo, porque evita la sobrecarga de la reprogramación de procesos del sistema operativo. Es ineficaz si el bloqueo se mantiene durante mucho tiempo o si el progreso del subproceso que mantiene el bloqueo depende de la preferencia del subproceso bloqueado.

Las cerraduras normalmente requieren soporte de hardware para una implementación eficiente. Este soporte suele tomar la forma de una o más instrucciones atómicas como " probar y configurar ", " buscar y agregar " o " comparar e intercambiar ". Estas instrucciones permiten que un solo proceso pruebe si el candado está libre y, si está libre, adquirir el candado en una sola operación atómica.

Las arquitecturas monoprocesador tienen la opción de utilizar secuencias ininterrumpibles de instrucciones (usando instrucciones especiales o prefijos de instrucciones para desactivar las interrupciones temporalmente), pero esta técnica no funciona para máquinas multiprocesador de memoria compartida. El soporte adecuado para bloqueos en un entorno multiprocesador puede requerir soporte de hardware o software bastante complejo, con importantes problemas de sincronización .

La razón por la que se requiere una operación atómica es por la concurrencia, donde más de una tarea ejecuta la misma lógica. Por ejemplo, considere el siguiente código C :

if ( lock == 0 ) { // bloqueo libre, configúrelo lock = myPID ; }        

El ejemplo anterior no garantiza que la tarea tenga el bloqueo, ya que más de una tarea puede estar probando el bloqueo al mismo tiempo. Dado que ambas tareas detectarán que el bloqueo está libre, ambas tareas intentarán establecer el bloqueo, sin saber que la otra tarea también lo está configurando. Los algoritmos de Dekker o Peterson son posibles sustitutos si las operaciones de bloqueo atómico no están disponibles.

El uso descuidado de las cerraduras puede provocar un punto muerto o un bloqueo activo . Se pueden utilizar varias estrategias para evitar o recuperarse de interbloqueos o bloqueos activos, tanto en tiempo de diseño como en tiempo de ejecución . (La estrategia más común es estandarizar las secuencias de adquisición de bloqueos para que las combinaciones de bloqueos interdependientes siempre se adquieran en un orden de "cascada" específicamente definido).

Algunos idiomas admiten bloqueos sintácticamente. A continuación se muestra un ejemplo en C# :

Cuenta de clase pública // Este es un monitor de una cuenta { balance decimal privado = 0 ; objeto privado _balanceLock = nuevo objeto ();               public void Deposit ( cantidad decimal ) { // Solo un hilo a la vez puede ejecutar esta declaración. bloquear ( _balanceLock ) { _saldo += monto ; } }              public void Withdraw ( cantidad decimal ) { // Solo un hilo a la vez puede ejecutar esta declaración. bloquear ( _balanceLock ) { _saldo -= cantidad ; } } }             

El código lock(this)puede generar problemas si se puede acceder públicamente a la instancia. [1]

Al igual que Java , C# también puede sincronizar métodos completos mediante el uso del atributo MethodImplOptions.Synchronized. [2] [3]

[MethodImpl(MethodImplOptions.Synchronized)] public void SomeMethod () { // hacer cosas }   

Granularidad

Antes de comenzar con la granularidad de los bloqueos, es necesario comprender tres conceptos sobre los bloqueos:

Existe una compensación entre disminuir la sobrecarga de bloqueo y disminuir la contención de bloqueo al elegir la cantidad de bloqueos en sincronización.

Una propiedad importante de una cerradura es su granularidad . La granularidad es una medida de la cantidad de datos que protege el bloqueo. En general, elegir una granularidad gruesa (una pequeña cantidad de bloqueos, cada uno de los cuales protege un gran segmento de datos) da como resultado una menor sobrecarga de bloqueo cuando un solo proceso accede a los datos protegidos, pero un peor rendimiento cuando se ejecutan varios procesos al mismo tiempo. Esto se debe a una mayor contención de bloqueos . Cuanto más burdo sea el bloqueo, mayor será la probabilidad de que detenga el desarrollo de un proceso no relacionado. Por el contrario, el uso de una granularidad fina (una mayor cantidad de bloqueos, cada uno de los cuales protege una cantidad bastante pequeña de datos) aumenta la sobrecarga de los propios bloqueos, pero reduce la contención de bloqueos. El bloqueo granular donde cada proceso debe contener múltiples bloqueos de un conjunto común de bloqueos puede crear dependencias de bloqueo sutiles. Esta sutileza puede aumentar la posibilidad de que un programador, sin saberlo, introduzca un punto muerto . [ cita necesaria ]

En un sistema de gestión de bases de datos , por ejemplo, un candado podría proteger, en orden de granularidad decreciente, parte de un campo, un campo, un registro, una página de datos o una tabla completa. La granularidad gruesa, como el uso de bloqueos de tabla, tiende a brindar el mejor rendimiento para un solo usuario, mientras que la granularidad fina, como los bloqueos de registros, tiende a brindar el mejor rendimiento para varios usuarios.

Bloqueos de base de datos

Los bloqueos de bases de datos se pueden utilizar como medio para garantizar la sincronicidad de las transacciones. es decir, cuando se hace que el procesamiento de transacciones sea concurrente (transacciones entrelazadas), el uso de bloqueos de 2 fases garantiza que la ejecución concurrente de la transacción resulte equivalente a algún orden en serie de la transacción. Sin embargo, los interbloqueos se convierten en un desafortunado efecto secundario del bloqueo de bases de datos. Los interbloqueos se evitan predeterminando el orden de bloqueo entre transacciones o se detectan mediante gráficos de espera . Una alternativa al bloqueo para la sincronización de la base de datos y al mismo tiempo evitar interbloqueos implica el uso de marcas de tiempo globales totalmente ordenadas.

Existen mecanismos empleados para gestionar las acciones de varios usuarios simultáneos en una base de datos; el propósito es evitar la pérdida de actualizaciones y lecturas sucias. Los dos tipos de bloqueo son el bloqueo pesimista y el bloqueo optimista :

Dónde utilizar el bloqueo pesimista: se utiliza principalmente en entornos donde la contención de datos (el grado de solicitudes de los usuarios al sistema de base de datos en un momento dado) es intensa; donde el costo de proteger los datos mediante bloqueos es menor que el costo de revertir las transacciones, si se producen conflictos de concurrencia. La concurrencia pesimista se implementa mejor cuando los tiempos de bloqueo son cortos, como en el procesamiento programático de registros. La concurrencia pesimista requiere una conexión persistente a la base de datos y no es una opción escalable cuando los usuarios interactúan con datos, porque los registros pueden estar bloqueados durante períodos de tiempo relativamente largos. No es apropiado para su uso en el desarrollo de aplicaciones web.
Dónde utilizar el bloqueo optimista: esto es apropiado en entornos donde hay poca contención de datos o donde se requiere acceso de solo lectura a los datos. La concurrencia optimista se utiliza ampliamente en .NET para abordar las necesidades de aplicaciones móviles y desconectadas, [4] donde bloquear filas de datos durante períodos prolongados de tiempo sería inviable. Además, mantener bloqueos de registros requiere una conexión persistente al servidor de la base de datos, lo que no es posible en aplicaciones desconectadas.

Tabla de compatibilidad de cerraduras

Existen varias variaciones y refinamientos de estos tipos principales de cerraduras, con sus respectivas variaciones de comportamiento de bloqueo. Si un primer candado bloquea otro candado, los dos candados se denominan incompatibles ; De lo contrario, las cerraduras son compatibles . A menudo, los tipos de cerraduras que bloquean las interacciones se presentan en la literatura técnica mediante una tabla de compatibilidad de cerraduras . El siguiente es un ejemplo con los principales tipos de bloqueo comunes:

Comentario: En algunas publicaciones, las entradas de la tabla simplemente están marcadas como "compatibles" o "incompatibles", o respectivamente "sí" o "no". [5]

Desventajas

La protección de recursos basada en bloqueos y la sincronización de subprocesos/procesos tienen muchas desventajas:

Algunas estrategias de control de concurrencia evitan algunos o todos estos problemas. Por ejemplo, un embudo o serializar tokens puede evitar el mayor problema: los interbloqueos. Las alternativas al bloqueo incluyen métodos de sincronización sin bloqueo , como técnicas de programación sin bloqueo y memoria transaccional . Sin embargo, estos métodos alternativos a menudo requieren que los mecanismos de bloqueo reales se implementen en un nivel más fundamental del software operativo. Por lo tanto, es posible que solo liberen al nivel de la aplicación de los detalles de la implementación de bloqueos, y los problemas enumerados anteriormente aún deben tratarse debajo de la aplicación.

En la mayoría de los casos, el bloqueo adecuado depende de que la CPU proporcione un método de sincronización del flujo de instrucciones atómicas (por ejemplo, la adición o eliminación de un elemento en una canalización requiere que todas las operaciones contemporáneas que necesiten agregar o eliminar otros elementos en la canalización se suspendan durante la manipulación del contenido de la memoria necesaria para agregar o eliminar el elemento específico). Por lo tanto, una aplicación a menudo puede ser más robusta cuando reconoce las cargas que impone a un sistema operativo y es capaz de reconocer amablemente el informe de demandas imposibles. [ cita necesaria ]

Falta de componibilidad

Uno de los mayores problemas de la programación basada en bloqueos es que "los bloqueos no componen ": es difícil combinar módulos pequeños y correctos basados ​​en bloqueos en programas más grandes igualmente correctos sin modificar los módulos o al menos conocer sus componentes internos. Simon Peyton Jones (un defensor de la memoria transaccional de software ) da el siguiente ejemplo de una aplicación bancaria: [6] diseñar una clase Cuenta que permita a múltiples clientes simultáneos depositar o retirar dinero a una cuenta; y dar un algoritmo para transferir dinero de una cuenta a otra. La solución basada en bloqueos para la primera parte del problema es:

cuenta de clase : saldo de miembro : mutex de miembro entero : bloqueo método de depósito (n: entero) mutex.bloqueo() saldo ← saldo + n mutex.desbloquear() método retirar (n: entero) depósito(-n)

La segunda parte del problema es mucho más complicada. Una rutina de transferencia que sea correcta para programas secuenciales sería

transferencia de función (de: Cuenta, a: Cuenta, monto: Entero) de.retirar(monto) a.depositar(monto)

En un programa concurrente, este algoritmo es incorrecto porque cuando un hilo está a la mitad de la transferencia , otro puede observar un estado en el que se ha retirado un monto de la primera cuenta, pero aún no se ha depositado en la otra cuenta: el dinero ha desaparecido del sistema. Este problema solo se puede solucionar por completo tomando bloqueos en ambas cuentas antes de cambiar cualquiera de las dos cuentas, pero luego los bloqueos deben tomarse de acuerdo con algún orden global arbitrario para evitar un punto muerto:

transferencia de función (de: Cuenta, a: Cuenta, monto: Entero) si de < a // ordenamiento arbitrario en las cerraduras desde.lock() para bloquear() demás para bloquear() desde.lock() de.retirar(monto) a.depositar(monto) desde.desbloquear() para desbloquear()

Esta solución se vuelve más complicada cuando hay más bloqueos involucrados y la función de transferencia necesita conocer todos los bloqueos, por lo que no se pueden ocultar .

Ayuda de idioma

Los lenguajes de programación varían en su soporte para la sincronización:

Mutexes versus semáforos

Un mutex es un mecanismo de bloqueo que a veces utiliza la misma implementación básica que el semáforo binario. Las diferencias entre ellos están en cómo se utilizan. Si bien un semáforo binario puede denominarse coloquialmente mutex, un verdadero mutex tiene un caso de uso y una definición más específicos, ya que se supone que solo la tarea que bloqueó el mutex debe desbloquearlo. Esta restricción tiene como objetivo manejar algunos problemas potenciales del uso de semáforos:

  1. Inversión de prioridad : si el mutex sabe quién lo bloqueó y se supone que debe desbloquearlo, es posible promover la prioridad de esa tarea siempre que una tarea de mayor prioridad comience a esperar en el mutex.
  2. Terminación prematura de la tarea: los mutex también pueden proporcionar seguridad de eliminación, donde la tarea que contiene el mutex no se puede eliminar accidentalmente. [ cita necesaria ]
  3. Interbloqueo de terminación: si una tarea de retención de exclusión mutua finaliza por cualquier motivo, el sistema operativo puede liberar la exclusión mutua y señalar las tareas en espera de esta condición.
  4. Punto muerto de recursividad: una tarea puede bloquear un mutex reentrante varias veces mientras lo desbloquea un número igual de veces.
  5. Liberación accidental: se genera un error en la liberación del mutex si la tarea de liberación no es su propietario.

Ver también

Referencias

  1. ^ "Declaración de bloqueo (referencia de C#)".
  2. ^ "ThreadPoolPriority y MethodImplAttribute". MSDN. pag. ?? . Consultado el 22 de noviembre de 2011 .
  3. ^ "C# desde la perspectiva de un desarrollador de Java". Archivado desde el original el 2 de enero de 2013 . Consultado el 22 de noviembre de 2011 .
  4. ^ "Diseño de componentes de niveles de datos y transferencia de datos a través de niveles". Microsoft . Agosto de 2002. Archivado desde el original el 8 de mayo de 2008 . Consultado el 30 de mayo de 2008 .
  5. ^ "Protocolo de control de concurrencia basado en bloqueo en DBMS". Geeks para Geeks . 2018-03-07 . Consultado el 28 de diciembre de 2023 .
  6. ^ Peyton Jones, Simón (2007). "Hermosa concurrencia" (PDF) . En Wilson, Greg; Oram, Andy (eds.). Hermoso código: los programadores líderes explican cómo piensan . O'Reilly.
  7. ^ ISO/IEC 8652:2007. "Unidades Protegidas y Objetos Protegidos". Manual de referencia de Ada 2005 . Consultado el 27 de febrero de 2010 . Un objeto protegido proporciona acceso coordinado a datos compartidos, a través de llamadas a sus operaciones protegidas visibles, que pueden ser subprogramas protegidos o entradas protegidas.{{cite book}}: Mantenimiento CS1: nombres numéricos: lista de autores ( enlace )
  8. ^ ISO/IEC 8652:2007. "Ejemplo de tareas y sincronización". Manual de referencia de Ada 2005 . Consultado el 27 de febrero de 2010 .{{cite book}}: Mantenimiento CS1: nombres numéricos: lista de autores ( enlace )
  9. ^ Marshall, Dave (marzo de 1999). "Cerraduras de exclusión mutua" . Consultado el 30 de mayo de 2008 .
  10. ^ "Sincronizar". msdn.microsoft.com . Consultado el 30 de mayo de 2008 .
  11. ^ "Sincronización". Microsistemas solares . Consultado el 30 de mayo de 2008 .
  12. ^ "Referencia de subprocesos de Apple". Apple Inc . Consultado el 17 de octubre de 2009 .
  13. ^ "Referencia de NSLock". Apple Inc . Consultado el 17 de octubre de 2009 .
  14. ^ "Referencia de NSRecursiveLock". Apple Inc . Consultado el 17 de octubre de 2009 .
  15. ^ "Referencia de NSConditionLock". Apple Inc . Consultado el 17 de octubre de 2009 .
  16. ^ "Referencia del protocolo NSLocking". Apple Inc . Consultado el 17 de octubre de 2009 .
  17. ^ "rebaño".
  18. ^ "La clase Mutex". Archivado desde el original el 4 de julio de 2017 . Consultado el 29 de diciembre de 2016 .
  19. ^ Lundh, Fredrik (julio de 2007). "Mecanismos de sincronización de subprocesos en Python". Archivado desde el original el 1 de noviembre de 2020 . Consultado el 30 de mayo de 2008 .
  20. ^ John Reid (2010). "Coarrays en el próximo estándar Fortran" (PDF) . Consultado el 17 de febrero de 2020 .
  21. ^ "Programación de Ruby: subprocesos y procesos". 2001 . Consultado el 30 de mayo de 2008 .
  22. ^ "std::sync::Mutex - Óxido". doc.rust-lang.org . Consultado el 3 de noviembre de 2020 .
  23. ^ "Simultaneidad de estado compartido: el lenguaje de programación Rust". doc.rust-lang.org . Consultado el 3 de noviembre de 2020 .
  24. ^ Marlow, Simon (agosto de 2013). "Concurrencia básica: subprocesos y MVars". Programación Paralela y Concurrente en Haskell. Medios O'Reilly . ISBN 9781449335946.
  25. ^ Marlow, Simon (agosto de 2013). "Memoria transaccional de software". Programación Paralela y Concurrente en Haskell. Medios O'Reilly . ISBN 9781449335946.
  26. ^ "paquete de sincronización - sincronización - pkg.go.dev". pkg.go.dev . Consultado el 23 de noviembre de 2021 .

enlaces externos