Búsqueda de sitios web

Diferencia entre objetos Lock y Rlock


La programación concurrente implica la ejecución de múltiples subprocesos o procesos simultáneamente, lo que puede generar desafíos como condiciones de carrera e inconsistencias de datos. Para solucionar estos problemas, Python proporciona primitivas de sincronización, incluidos los objetos Lock y RLock. Si bien ambos objetos sirven para controlar el acceso a recursos compartidos, difieren en comportamiento y uso.

El objeto Lock es un mecanismo fundamental de exclusión mutua. Permite que varios subprocesos adquieran y liberen el bloqueo, pero solo un subproceso puede mantener el bloqueo en un momento dado. Cuando un subproceso intenta adquirir un candado que ya tiene otro subproceso, se bloqueará hasta que el candado esté disponible. Esto garantiza que la sección crítica del código protegida por el bloqueo sea ejecutada por un solo subproceso a la vez.

Por otro lado, el objeto RLock amplía la funcionalidad de Lock al introducir reentrada o bloqueo recursivo. La reentrada permite que un subproceso que ya posee el bloqueo lo adquiera nuevamente sin provocar un punto muerto. Esto es particularmente útil en escenarios donde las llamadas a funciones anidadas o secciones de código requieren múltiples niveles de bloqueo. Con un objeto RLock, un subproceso puede adquirir el bloqueo varias veces y debe liberarlo la misma cantidad de veces antes de que esté disponible para otros subprocesos. Esto garantiza que el bloqueo permanezca retenido hasta que se realicen todas las liberaciones correspondientes.

Bloquear objeto

El objeto Lock es un mecanismo de exclusión mutua fundamental en el módulo de subprocesamiento de Python. Su objetivo principal es controlar el acceso a recursos compartidos en un entorno concurrente, asegurando que sólo un hilo pueda mantener el bloqueo en un momento dado. Esto garantiza la ejecución exclusiva de una sección crítica de código protegida por el bloqueo.

El comportamiento del objeto Lock es sencillo. Varios subprocesos pueden intentar adquirir y liberar el bloqueo, pero sólo un subproceso logrará adquirirlo. Si un subproceso intenta adquirir un candado que ya tiene otro subproceso, se bloqueará y se pondrá en estado de espera hasta que el candado esté disponible. Una vez adquirido el bloqueo, el subproceso puede ingresar de forma segura a la sección crítica y realizar las operaciones necesarias en el recurso compartido. Después de completar la sección crítica, el bloqueo se libera, permitiendo que otros subprocesos lo adquieran.

El objeto Lock proporciona dos métodos esenciales: adquirir() y liberar(). El método adquirir() se utiliza para adquirir el bloqueo. Si el bloqueo ya está retenido por otro hilo, el hilo que llama se bloqueará y esperará hasta que se libere el bloqueo. Una vez adquirido el bloqueo, el subproceso puede continuar con la ejecución de la sección crítica de código. Después de completar la sección crítica, se llama al método release() para liberar el bloqueo y dejarlo disponible para que otros subprocesos lo adquieran.

Una cosa importante a tener en cuenta sobre los objetos Lock es que no admiten la reentrada. La reentrada se refiere a la capacidad de un subproceso de adquirir el mismo bloqueo varias veces sin provocar un punto muerto. En el caso de Lock, si un hilo que ya posee el bloqueo intenta adquirirlo nuevamente, se producirá un punto muerto en el que el hilo no podrá continuar, lo que provocará que el programa se cuelgue. Por lo tanto, los objetos Lock son adecuados para escenarios donde no se requiere un comportamiento reentrante, como sincronización simple o situaciones donde no hay llamadas a funciones anidadas.

Objeto RLlock

El objeto RLock, abreviatura de "bloqueo reentrante", es una extensión del objeto Lock que aborda la limitación del bloqueo no reentrante. Proporciona soporte para la reentrada, lo que permite que un subproceso adquiera el bloqueo varias veces sin provocar un punto muerto.

La característica clave del objeto RLock es su capacidad para manejar adquisiciones de bloqueos recursivos. Esto significa que un hilo puede adquirir el bloqueo varias veces de forma anidada. Cada adquisición debe coincidir con un número equivalente de liberaciones para renunciar al bloqueo. Este comportamiento es particularmente útil en escenarios donde las llamadas a funciones anidadas o secciones de código requieren múltiples niveles de bloqueo.

El objeto RLock proporciona los mismos métodos adquirir() y release() que el objeto Lock, lo que facilita el trabajo con él. Además, introduce dos métodos adicionales: adquirir() con un parámetro de bloqueo y liberar() con un parámetro de recuento.

El método adquirir() con un parámetro de bloqueo permite un control detallado sobre la adquisición de bloqueos. Al configurar blocking=False, un subproceso puede intentar adquirir el bloqueo, pero no se bloqueará si el bloqueo ya está en manos de otro subproceso. Esto permite que el subproceso realice acciones alternativas o ejecute diferentes rutas de código cuando el bloqueo no está disponible de inmediato.

El método release() con un parámetro de recuento permite liberar el bloqueo un número específico de veces. Esto puede resultar útil en situaciones en las que un subproceso necesita liberar adquisiciones de bloqueos anidados de forma incremental o cuando el número de adquisiciones y liberaciones puede variar dinámicamente.

Ejemplo

Aquí hay un fragmento de código de ejemplo que demuestra el uso de objetos Lock y RLock en Python:

import threading

# Shared resource
shared_resource = 0

# Lock objects
lock = threading.Lock()
rlock = threading.RLock()

# Function using Lock
def increment_with_lock():
    global shared_resource
    lock.acquire()
    try:
        shared_resource += 1
    finally:
        lock.release()

# Function using RLock
def increment_with_rlock():
    global shared_resource
    rlock.acquire()
    try:
        shared_resource += 1
        rlock.acquire()  # Nested acquisition
        try:
            shared_resource += 1
        finally:
            rlock.release()  # Nested release
    finally:
        rlock.release()

# Create multiple threads to increment shared_resource
num_threads = 5

# Using Lock
threads_with_lock = []
for _ in range(num_threads):
    thread = threading.Thread(target=increment_with_lock)
    threads_with_lock.append(thread)
    thread.start()

for thread in threads_with_lock:
    thread.join()

print("Value of shared_resource using Lock:", shared_resource)

# Reset shared_resource
shared_resource = 0

# Using RLock
threads_with_rlock = []
for _ in range(num_threads):
    thread = threading.Thread(target=increment_with_rlock)
    threads_with_rlock.append(thread)
    thread.start()

for thread in threads_with_rlock:
    thread.join()

print("Value of shared_resource using RLock:", shared_resource)

En este código, tenemos un recurso compartido (shared_resource) que se incrementa en varios subprocesos. La función increment_with_lock() demuestra el uso de un objeto Lock para garantizar el acceso exclusivo a la sección crítica donde se incrementa el recurso compartido. De manera similar, la función increment_with_rlock() muestra el uso de un objeto RLock, lo que permite adquisiciones y liberaciones de bloqueos recursivos.

Elegir entre bloqueo y RLock

Al decidir entre usar un Lock o un RLock en su programa concurrente, considere las siguientes pautas 

  • Utilice un bloqueo cuando solo necesite un mecanismo básico de exclusión mutua y no se requiera reentrada. Los objetos de bloqueo son livianos y adecuados para escenarios de sincronización simples donde no hay llamadas a funciones anidadas ni múltiples niveles de bloqueo.

  • Utilice un RLock cuando tenga llamadas a funciones anidadas o secciones de código que requieran múltiples niveles de bloqueo. RLock permite que un subproceso adquiera el bloqueo de forma recursiva, lo que garantiza una sincronización adecuada en tales escenarios. Permite el comportamiento de reentrada y evita interbloqueos al adquirir el bloqueo varias veces.

Conclusión

En la programación concurrente de Python, los objetos Lock y RLock sirven como primitivas de sincronización para controlar el acceso a los recursos compartidos. Mientras que el objeto Lock proporciona exclusión mutua básica, el objeto RLock amplía su funcionalidad ofreciendo soporte de reentrada. Comprender las diferencias entre estos objetos es crucial para escribir código sólido y seguro para subprocesos.