Python Deadlock Lernen

Beispiele

Dieser Artikel fasst Python-Deadlock- Artikel zusammen und erklärt einige der unverständlichen Inhalte.
Zählen Sie zunächst eine Situation auf, in der ein Deadlock auftritt

死锁的一个原因是互斥锁。假设银行系统中,用户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()

In Multithread-Programmen wird ein großer Teil des Deadlock-Problems dadurch verursacht, dass Threads mehrere Sperren gleichzeitig erwerben. Beispiel: Ein Thread erwirbt die erste Sperre und blockiert dann beim Erwerb der zweiten Sperre. Dieser Thread blockiert möglicherweise die Ausführung anderer Threads, was zum Tod des gesamten Programms führt. Eine Lösung für das Deadlock-Problem besteht darin, jeder Sperre im Programm eine eindeutige ID zuzuweisen und dann nur die Verwendung mehrerer Sperren in aufsteigender Reihenfolge zuzulassen . Diese Regel ist mit einem Kontextmanager sehr einfach zu implementieren

Schauen Sie sich zunächst den Kontextmanager an. @Contextmanager
Die vom Dekorateur dekorierte Funktion ist in drei Teile unterteilt:

  1. Der Codeblock in der with-Anweisung führt den Code vor der Ausbeute in der Funktion aus
  2. Der von Yield zurückgegebene Inhalt wird nach as in die Variable kopiert
  3. Nachdem der with-Code-Block ausgeführt wurde, wird der Code nach der Ausbeute in der Funktion ausgeführt

Lösung

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):]

Natürlich gibt es bestimmte Probleme beim Erwerb von Sperren in aufsteigender Reihenfolge, siehe folgenden Code

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的子线程将随主线程一起结束,而不论
是否运行完成。

Das Ergebnis des ausgeführten Codes ist eine Ausnahme
Fügen Sie hier eine Bildbeschreibung ein
. Der Grund für den Absturz ist, dass jeder Thread die Sperre aufzeichnet, die er erworben hat. Die Funktion purchase () überprüft die Liste der zuvor erfassten Sperren. Da die Sperren in aufsteigender Reihenfolge erfasst werden, geht die Funktion davon aus, dass die ID der zuvor erfassten Sperre kleiner sein muss als die der neu erfassten Sperre, und eine Ausnahme wird ausgelöst.
Der Grund für das obige Ergebnis ist, dass die Ausnahme zum ersten Mal in Thread-2 aufgetreten ist und der Thread unterbrochen wurde. Danach gibt es keinen Deadlock mehr, da nur ein Thread Thread-1 ausgeführt wird. Über den Kontextmanager wird die Sperre nach jedem internen Erfassungsaufruf automatisch aufgehoben, sodass kein Deadlock auftritt.

Zusammenfassung:

Das Vermeiden von Deadlocks ist eine Möglichkeit, das Deadlock-Problem zu lösen. Wenn der Prozess die Sperre erwirbt, wird sie in strikt aufsteigender Reihenfolge der Objekt-ID angeordnet. Nach einem mathematischen Beweis wird sichergestellt, dass das Programm nicht in den Deadlock-Status wechselt. Die Hauptidee zur Vermeidung von Deadlocks besteht darin, dass durch einfaches Hinzufügen von Sperren in der Reihenfolge der Erhöhung der Objekt-ID keine zirkuläre Abhängigkeit entsteht. Eine zirkuläre Abhängigkeit ist eine notwendige Bedingung für einen Deadlock, um zu verhindern, dass das Programm in einen Deadlock-Status wechselt.

Veröffentlichte 16 Originalartikel · Like1 · Besuche 375

Ich denke du magst

Origin blog.csdn.net/qq_41174940/article/details/104620663
Empfohlen
Rangfolge