Solución:
La principal diferencia es que un Lock
solo se puede adquirir una vez. No se puede volver a adquirir hasta que se publique. (Después de su lanzamiento, cualquier hilo puede volver a adquirirlo).
Un RLock
por otro lado, se pueden adquirir varias veces, por el mismo hilo. Debe liberarse el mismo número de veces para poder “desbloquearlo”.
Otra diferencia es que un adquirido Lock
puede ser liberado por cualquier hilo, mientras que un adquirido RLock
sólo puede ser liberado por el hilo que lo adquirió.
Aquí hay un ejemplo que demuestra por qué RLock
es útil a veces. Suponga que tiene:
def f():
g()
h()
def g():
h()
do_something1()
def h():
do_something2()
Digamos todos f
, g
, y h
están público (es decir, puede ser llamado directamente por un llamador externo), y todos ellos requieren sincronización.
Usando un Lock
, puedes hacer algo como:
lock = Lock()
def f():
with lock:
_g()
_h()
def g():
with lock:
_g()
def _g():
_h()
do_something1()
def h():
with lock:
_h()
def _h():
do_something2()
Básicamente, desde f
no puedo llamar g
después de adquirir el bloqueo, debe llamar a una versión “sin procesar” de g
(es decir _g
). Así que terminas con una versión “sincronizada” y una versión “sin procesar” de cada función.
Usando un RLock
resuelve elegantemente el problema:
lock = RLock()
def f():
with lock:
g()
h()
def g():
with lock:
h()
do_something1()
def h():
with lock:
do_something2()
Para ampliar la respuesta de shx2, la razón por qué quieres usar uno frente al otro podría ser lo siguiente:
Un habitual Lock
(mutex) suele ser más rápido y seguro.
La razón para usar RLock
es evitar un bloqueo muerto debido, por ejemplo, a la recursividad. Por ejemplo, pongamos un candado en la función factorial recursiva. (es cierto que algo artificial)
from threading import Lock
lock = Lock()
def factorial(n):
assert n > 0
if n == 1:
return 1
with lock:
out = n * factorial(n - 1)
return out
Esta función provocará un bloqueo muerto debido a la llamada recursiva. Si usamos RLock
en cambio, sin embargo, las llamadas recursivas pueden volver a ingresar al mismo bloqueo tantas veces como sea necesario. De ahí el nombre reentrante (o recursivo) cerrar con llave.