Bloqueo de hilo en Python

Muhammad Maisam Abbas 30 enero 2023
  1. Condición de carrera en Python
  2. Bloqueo de hilo en Python
  3. Bloqueo de hilo usando with lock: en Python
Bloqueo de hilo en Python

Este tutorial discutirá diferentes métodos para utilizar un bloqueo de hilo en Python.

Condición de carrera en Python

Una condición de carrera es un problema que ocurre cuando varios subprocesos intentan modificar la misma variable compartida. Todos los hilos leen el mismo valor de la variable compartida al mismo tiempo. Luego, todos los hilos intentan modificar el valor de la variable compartida. Pero, la variable solo termina almacenando el valor del último hilo porque sobrescribe el valor escrito por el hilo anterior. En este sentido, hay una carrera entre todos los hilos para ver cuál modifica al final el valor de la variable. Este fenómeno se demuestra con un ejemplo en el siguiente código.

from threading import Thread

counter = 0


def increase(by):
    global counter
    local_counter = counter
    local_counter += by
    counter = local_counter
    print(f"counter={counter}")


t1 = Thread(target=increase, args=(10,))
t2 = Thread(target=increase, args=(20,))

t1.start()
t2.start()

t1.join()
t2.join()

print(f"The final counter is {counter}")

Producción :

counter=10
counter=20
The final counter is 20

Tenemos una variable compartida global contador = 0 y dos subprocesos t1 y t2. El hilo t1 intenta incrementar el valor de counter en 10 y el hilo t2 intenta incrementar el valor de counter en 20. En el código anterior, ejecutamos ambos hilos simultáneamente e intentamos modificar el valor de counter. Según la lógica anterior, el valor final de counter debe tener el valor 30. Pero, debido a la condición de carrera, el counter es 10 o 20.

Bloqueo de hilo en Python

El bloqueo de rosca se utiliza para evitar la condición de carrera. El bloqueo de subprocesos bloquea el acceso a una variable compartida cuando es utilizado por un subproceso para que ningún otro subproceso no pueda acceder a él y luego elimina el bloqueo cuando el subproceso no está utilizando la variable compartida para que la variable esté disponible para otros subprocesos para su procesamiento. La clase Lock dentro del módulo de subprocesos se utiliza para crear un bloqueo de subprocesos en Python. El método acquire() se usa para bloquear el acceso a una variable compartida, y el método release() se usa para desbloquear el bloqueo. El método release() arroja una excepción RuntimeError si se usa en un bloqueo desbloqueado.

from threading import Thread, Lock

counter = 0


def increase(by, lock):
    global counter

    lock.acquire()

    local_counter = counter
    local_counter += by
    counter = local_counter
    print(f"counter={counter}")

    lock.release()


lock = Lock()

t1 = Thread(target=increase, args=(10, lock))
t2 = Thread(target=increase, args=(20, lock))

t1.start()
t2.start()

t1.join()
t2.join()

print(f"The final counter is {counter}")

Producción :

counter=10
counter=30
The final counter is 30

Creamos una variable compartida globalmente compartida counter=0 y dos subprocesos t1 y t2. Ambos hilos tienen como objetivo la misma función increase(). La función increase(by, lock) toma dos parámetros. El primer parámetro es la cantidad en la que se incrementará el counter, y el segundo parámetro es una instancia de la clase Lock. Además de las declaraciones anteriores, también creamos una instancia lock de la clase Lock dentro del módulo threading de Python. Este parámetro block en la función increase(by, lock) bloquea el acceso a la variable counter con la función lock.acquire() mientras es modificada por cualquier hilo y desbloquea el bloqueo con la función lock.release() cuando un hilo ha modificado la variable counter. El hilo t1 incrementa el valor de counter en 10, y el hilo t2 incrementa el valor de counter en 20.

Debido al bloqueo del hilo, la condición de carrera no ocurre y el valor final del counter es 30.

Bloqueo de hilo usando with lock: en Python

El problema con el método anterior es que tenemos que desbloquear cuidadosamente cada variable bloqueada cuando un hilo ha completado el procesamiento. Si no se hace correctamente, nuestra variable compartida solo será accedida por el primer hilo, y ningún otro hilo tendrá acceso a la variable compartida. Este problema se puede evitar utilizando la gestión de contexto. Podemos usar with lock: y colocar todo nuestro código crítico dentro de este bloque. Esta es una forma mucho más fácil de prevenir condiciones de carrera. El siguiente fragmento de código muestra el uso de with lock: para prevenir la condición de carrera en Python.

from threading import Thread, Lock

counter = 0


def increase(by, lock):
    global counter

    with lock:
        local_counter = counter
        local_counter += by
        counter = local_counter
    print(f"counter={counter}")


lock = Lock()

t1 = Thread(target=increase, args=(10, lock))
t2 = Thread(target=increase, args=(20, lock))

t1.start()
t2.start()

t1.join()
t2.join()

print(f"The final counter is {counter}")

Producción :

counter=10
counter=30
The final counter is 30

Colocamos nuestro código para incrementar counter dentro del bloque with lock:. El hilo t1 incrementa el valor de counter en 10, y el hilo t2 incrementa el valor de counter en 20. La condición de carrera no ocurre, y el valor final de counter es 30. Además , No tenemos que preocuparnos por desbloquear el bloqueo del hilo.

Ambos métodos hacen su trabajo a la perfección, es decir, ambos métodos evitan que ocurra la condición de carrera, pero el segundo método es muy superior al primero porque nos evita el dolor de cabeza de lidiar con el bloqueo y desbloqueo de bloqueos de hilo. También es mucho más limpio de escribir y más fácil de leer que el primer método.

Muhammad Maisam Abbas avatar Muhammad Maisam Abbas avatar

Maisam is a highly skilled and motivated Data Scientist. He has over 4 years of experience with Python programming language. He loves solving complex problems and sharing his results on the internet.

LinkedIn

Artículo relacionado - Python Thread