Exemplos
Este artigo resume os artigos de impasse do Python e explica parte do conteúdo incompreensível.
Primeiro enumere uma situação em que ocorrerá um conflito
死锁的一个原因是互斥锁。假设银行系统中,用户a试图转账100块给用户b,
与此同时用户b试图转账200块给用户a,则可能产生死锁。
2个线程互相等待对方的锁,互相占用着资源不释放。
#coding=utf-8
import time
import threading
class Account:
def __init__(self, _id, balance, lock):
self.id = _id
self.balance = balance
self.lock = lock
def withdraw(self, amount):
self.balance -= amount
def deposit(self, amount):
self.balance += amount
def transfer(_from, to, amount):
if _from.lock.acquire():#锁住自己的账户
_from.withdraw(amount)
time.sleep(1)#让交易时间变长,2个交易线程时间上重叠,有足够时间来产生死锁
print('wait for lock...')
if to.lock.acquire():#锁住对方的账户
to.deposit(amount)
to.lock.release()
_from.lock.release()
print( 'finish...')
a = Account('a',1000, threading.Lock())
b = Account('b',1000, threading.Lock())
threading.Thread(target = transfer, args = (a, b, 100)).start()
threading.Thread(target = transfer, args = (b, a, 200)).start()
Nos programas multithread, grande parte do problema de deadlock é causada pelo encadeamento de vários bloqueios ao mesmo tempo. Por exemplo: um encadeamento adquire o primeiro bloqueio e, em seguida, bloqueia ao adquirir o segundo bloqueio, esse encadeamento pode bloquear a execução de outros encadeamentos, resultando na morte de todo o programa. Uma solução para o problema do impasse é atribuir um ID exclusivo para cada bloqueio no programa e permitir apenas que vários bloqueios sejam usados em ordem crescente.Esta regra é muito fácil de implementar usando um gerenciador de contexto
Primeiro, veja o que é o gerenciador de contexto @contextmanager.
A função decorada pelo decorador é dividida em três partes:
- O bloco de código na instrução with executa o código antes do rendimento na função
- O conteúdo retornado pelo rendimento é copiado para a variável depois de
- Depois que o bloco com código é executado, o código após o rendimento na função é executado
Solução
import threading
from contextlib import contextmanager
# Thread-local state to stored information on locks already acquired
_local = threading.local()
@contextmanager
def acquire(*locks):
# Sort locks by object identifier
locks = sorted(locks, key=lambda x: id(x))
# Make sure lock order of previously acquired locks is not violated
acquired = getattr(_local, 'acquired', [])
#如果出现新锁id大于旧锁id 报错
if acquired and max(id(lock) for lock in acquired) >= id(locks[0]):
raise RuntimeError('Lock Order Violation')
# Acquire all of the locks
acquired.extend(locks)
_local.acquired = acquired
try:
for lock in locks:
lock.acquire()
yield #无返回值 yield为空
finally:
# Release locks in reverse order of acquisition
for lock in reversed(locks):
lock.release()
del _local.acquired[-len(locks):]
Obviamente, existem certos problemas na aquisição de bloqueios em ordem crescente, consulte o código a seguir
import threading
x_lock = threading.Lock()
y_lock = threading.Lock()
def thread_1():
while True:
with acquire(x_lock):
print("acquire x_lock")
time.sleep(1)
with acquire(y_lock):
print('Thread-1')
time.sleep(1)
def thread_2():
while True:
with acquire(y_lock):
print('acquire y_lock')
time.sleep(1)
with acquire(x_lock):
print('Thread-2')
t1 = threading.Thread(target=thread_1)
t1.daemon = False
t1.start()
t2 = threading.Thread(target=thread_2)
t2.daemon = False
t2.start()
daemon:
(1)如果某个子线程的daemon属性为False,主线程结束时会检测该子线程是否结束,
如果该子线程还在运行,则主线程会等待它完成后再退出;
(2)如果某个子线程的daemon属性为True,主线程运行结束时不对这个子线程进行检
查而直接退出,同时所有daemon值为True的子线程将随主线程一起结束,而不论
是否运行完成。
O resultado da execução do código é uma exceção.O
motivo da falha é que cada thread registra o bloqueio que adquiriu. A função adquirir () verifica a lista de bloqueios adquiridos anteriormente.Como os bloqueios são adquiridos em ordem crescente, a função assume que o ID do bloqueio adquirido anteriormente deve ser menor que o bloqueio recém-adquirido e uma exceção é acionada.
O motivo do resultado acima é que a exceção apareceu pela primeira vez no Thread-2, causando a interrupção do thread. Depois disso, não haverá conflito, porque apenas um encadeamento Thread-1 está em execução e, através do gerenciador de contexto, o bloqueio será liberado automaticamente após cada chamada de aquisição interna, portanto, não haverá conflito.
Resumo:
Evitar deadlock é uma maneira de resolver o problema de deadlock.Quando o processo adquire o bloqueio, ele é organizado em ordem crescente estrita do ID do objeto.Depois de provas matemáticas, isso garante que o programa não entre no estado de deadlock. A idéia principal de evitar deadlock é que simplesmente adicionar bloqueios na ordem crescente de ID do objeto não criará uma dependência circular, e a dependência circular é uma condição necessária para o deadlock, para evitar que o programa entre em um estado de deadlock.