Chcesz coś wzdłuż następujących linii.
parent.py
import subprocess
c1= subprocess.Popen(["python", "child.py", "1"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
c2= subprocess.Popen(["python", "child.py", "2"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
out1, err1= c1.communicate("to 1: hit it!")
print " 1:", repr(out1)
print "*1:", repr(err1)
out2, err2= c2.communicate("to 2: ready, set, go!")
print " 2:", repr(out2)
print "*2:", repr(err2)
out1, err1= c1.communicate()
print " 1:", repr(out1)
print "*1:", repr(err1)
out2, err2= c2.communicate()
print " 2:", repr(out2)
print "*2:", repr(err2)
c1.wait()
c2.wait()
child.py
import yourDBconnection as dbapi2
def child1():
print "Child 1 start"
conn= dbapi2.connect(...)
c1= conn.cursor()
conn.begin() # turn off autocommit, start a transaction
ra= c1.execute("UPDATE A SET AC1='Achgd' WHERE AC1='AC1-1'")
print ra
print "Child1", raw_input()
rb= c1.execute("UPDATE B SET BC1='Bchgd' WHERE BC1='BC1-1'")
print rb
c1.close()
print "Child 1 finished"
def child2():
print "Child 2 start"
conn= dbapi2.connect(...)
c1= conn.cursor()
conn.begin() # turn off autocommit, start a transaction
rb= c1.execute("UPDATE B SET BC1='Bchgd' WHERE BC1='BC1-1'")
print rb
print "Child2", raw_input()
ra= c1.execute("UPDATE A SET AC1='Achgd' WHERE AC1='AC1-1'")
print ta
c1.close()
print "Child 2 finish"
try:
if sys.argv[1] == "1":
child1()
else:
child2()
except Exception, e:
print repr(e)
Uwaga symetria. Każde dziecko zaczyna od posiadania jednego zasobu. Następnie próbują zdobyć zasoby należące do kogoś innego. Możesz dla zabawy mieć 3 dzieci i 3 zasoby na naprawdę błędne koło.
Należy zauważyć, że trudność w opanowaniu sytuacji, w której występuje zakleszczenie. Jeśli twoje transakcje są krótkie i spójne, to impas jest bardzo trudny do osiągnięcia. Zakleszczenie wymaga (a) transakcji, która utrzymuje blokady przez dłuższy czas AND (b) transakcji, które nabywają zamki w niespójnym porządku. Zauważyłem, że najłatwiej jest zapobiec zakleszczeniom, utrzymując krótkie i spójne transakcje.
Należy również zwrócić uwagę na brak determinizmu. Nie można przewidzieć, które dziecko umrze z powodu impasu, a które będzie kontynuowane po śmierci drugiego. Tylko jeden z nich musi umrzeć, aby uwolnić potrzebne zasoby dla drugiego. Niektóre RDBMS twierdzą, że istnieje zasada oparta na liczbie zasobów przechowywanych bla bla bla, ale ogólnie, nigdy nie wiesz, w jaki sposób została wybrana ofiara.
Ponieważ dwa zapisy są w określonej kolejności, można oczekiwać, że dziecko 1 umrze jako pierwsze. Jednak nie możesz tego zagwarantować. To nie jest zakleszczenie, dopóki dziecko 2 nie spróbuje zdobyć zasobów pierwszego dziecka - sekwencja, która nabyła pierwsze, może nie określać, kto umiera.
Należy również pamiętać, że są to procesy, a nie wątki. Wątki - z powodu Pythona GIL - mogą zostać przypadkowo zsynchronizowane i wymagałyby wielu połączeń z numerem time.sleep(0.001)
, aby dać drugiemu wątkowi szansę na nadrobienie zaległości. Procesy - w tym przypadku - są nieco prostsze, ponieważ są w pełni niezależne.
To spowodowałoby po prostu błąd OperationalError: (1205, Przekroczono limit czasu oczekiwania na blokadę, spróbuj ponownie uruchomić transakcję "), nie? – Greg
@Greg: Myślę, że mówią o jednej sesji robiącej LOCK TABLE A, i kolejnej sesji robią LOCK TABLE B. W tej chwili muszą się zsynchronizować. Następnie sesja pierwsza próbuje TABELA BLOKÓW B. Gdy dwie próby sesji LOCK TABLE A - to zakleszczenie. –