Python | Cómo bloquear secciones críticas

Este artículo tiene como objetivo cómo bloquear los subprocesos y las secciones críticas en el programa dado para evitar condiciones de carrera. Por lo tanto, usar el objeto Lock en la biblioteca de subprocesos para hacer que los objetos mutables sean seguros para múltiples subprocesos.

Código #1:

import threading
  
class counter_share:
    '''
    multiple threads can share.
    '''
    def __init__(self, initial_key = 0):
        self._key = initial_key
        self._key_lock = threading.Lock()
          
    def incr(self, delta = 1):
        with self._key_lock:
            # Increasing the counter with lock
            self._key += delta
          
    def decr(self, delta = 1):
        with self._key_lock:
            # Decreasing the counter with lock
            self._key -= delta

El uso de una declaración with junto con el bloqueo garantiza la exclusión mutua. Por exclusión, se entiende que a la vez solo un subproceso (bajo la declaración) puede ejecutar el bloque de una declaración.
El bloqueo por la duración de las declaraciones previstas se adquiere y se libera cuando el flujo de control sale del bloque sangrado. La programación de subprocesos es inherentemente no determinista. Debido a esto, los datos corruptos al azar y la «condición de carrera» pueden resultar en una falla en el uso de bloqueos. Por lo tanto, cada vez que varios subprocesos acceden a un estado mutable compartido, siempre se deben usar bloqueos para evitar esto.

En el código Python más antiguo, es común ver bloqueos adquiridos y liberados explícitamente.

Código #2: Variante del código 1

import threading
  
class counter_share:
    # multiple threads can share counter objects
    def __init__(self, initial_key = 0):
        self._key = initial_key
        self._key_lock = threading.Lock()
          
    def incr(self, delta = 1):
        # Increasing the counter with lock
        self._key_lock.acquire()
        self._key += delta
        self._key_lock.release()
          
    def decr(self, delta = 1):
        # Decreasing the counter with lock
        self._key_lock.acquire()
        self._key -= delta
        self._key_lock.release()
  • En situaciones en las release()que no se llama al método o mientras se mantiene un bloqueo no se genera una excepción, la instrucción with es mucho menos propensa a errores.
  • No se permite que cada subproceso en un programa adquiera un bloqueo a la vez, esto puede evitar potencialmente los casos de interbloqueo. Introduzca una prevención de interbloqueo más avanzada en el programa si no es posible.
  • Las primitivas de sincronización, como los objetos RLock y Semaphore, se encuentran en la biblioteca de subprocesos.

Excepto el bloqueo del módulo simple, se está resolviendo un propósito más especial:

  • Un objeto de bloqueo RLock o reentrante es un bloqueo que el mismo subproceso puede adquirir varias veces.
  • Implementa el código principalmente sobre la base del bloqueo o la sincronización de una construcción conocida como «monitor». Solo un subproceso puede usar una función completa o los métodos de una clase mientras se mantiene el bloqueo, con este tipo de bloqueo.

Código #3: Implementando la clase SharedCounter.

import threading
  
class counter_share:
    # multiple threads can share counter objects
    _lock = threading.RLock()
      
    def __init__(self, initial_key = 0):
        self._key = initial_key
          
    def incr(self, delta = 1):
        # Increasing the counter with lock
        with SharedCounter._lock:
            self._key += delta
              
    def decr(self, delta = 1):
        # Decreasing the counter with lock
        with SharedCounter._lock:
            self.incr(-delta)
  • El bloqueo está destinado a sincronizar los métodos de la clase, a pesar de que el bloqueo está vinculado al estado mutable por instancia.
  • Solo hay un único bloqueo de nivel de clase compartido por todas las instancias de la clase en esta variante de código.
  • Solo se permite que un subproceso utilice los métodos de la clase a la vez.
  • está bien que los métodos llamen a otros métodos que también usan el bloqueo si ya tienen los bloqueos. (por ejemplo el decr()método).
  • Si hay una gran cantidad de contadores, la memoria es mucho más eficiente. Sin embargo, puede causar más contención de bloqueo en programas que usan una gran cantidad de subprocesos y realizan actualizaciones frecuentes de contadores.

Un elemento Semaphore es un crudo de sincronización que depende de un contador mutuo. El contador se aumenta al final del cuadrado. En el caso de que el contador sea cero, el avance se bloquea hasta que el contador se incrementa en otra string. Si el contador es distinto de cero, la explicación decrementa la cuenta y se permite que continúe una string.
En lugar de un bloqueo sencillo, los elementos de Semaphore son cada vez más valiosos para aplicaciones que incluyen el movimiento entre strings o la limitación. A pesar de que un semáforo se puede utilizar de manera similar a un bloqueo estándar, la naturaleza multifacética adicional en el uso afecta de manera contraria a la ejecución.

Código #4: Para limitar la cantidad de concurrencia en una parte del código, use un semáforo.

from threading import Semaphore
import urllib.request
  
# five threads are allowed to run at once (at max)
_fetch_url_sema = Semaphore(5)
  
def fetch_url(url):
    with _fetch_url_sema:
        return urllib.request.urlopen(url)

Publicación traducida automáticamente

Artículo escrito por manikachandna97 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *