Einfach alle Methoden-Aufrufe durch einen einzigen, gemeinsamen Mutex zu tunneln, skaliert meistens nicht besonders und führt oft zu einer Ent-Parallelisierung, weil ja alle Aufrufe dadurch hintereinander passieren. Es ist auch meistens besser, einen komplexeren
Monitor statt bloßer Locks zu verwenden, und zwar pro Menge von Attributen, deren Zustand einer gemeinsamen Invariante unterliegt. Diese Invariante ist nämlich idR. komplizierter, als bloß
"dieses Attribut darf nicht gleichzeitig gelesen und geschrieben werden", wie es durch ein Lock bzw. RLock ermöglicht würde. Einen Ringpuffer etwa, in den von einem Thread geschrieben und aus dem aus einem anderen Thread gelesen wird, kann man damit nicht bauen.
Threadsicherheit bei einem Ringpuffer bedeutet: wenn der Puffer
leer ist, muss gewartet werden, bis etwas hineingeschrieben wurde, erst dann kann man wieder lesen. Wenn er
voll ist, muss gewartet werden, bis etwas gelesen wurde, erst dann kann man wieder hineinschreiben. Dazu verwendet man eine Liste fester Größe und zwei Zeiger,
links und
rechts, die sich in Einer-Schritten in dieselbe Rchtung bewegen (wobei
rechts vorneweg marschieren darf), die sich aber gegenseitig nie überholen dürfen. Wenn
links == rechts ist, ist der Puffer
leer, wenn
links == (rechts + 1) % Größe des Puffers ist, ist er
voll. Damit in den jeweiligen Zuständen
leer und
voll gewartet wird, benötigt man einen
Monitor mit
wait sets, der über ein Condition Objekt kontrolliert wird, zB. so:
Code: Alles auswählen
from threading import Thread, Condition
class Ringbuffer(object):
def __init__(self, size):
assert size > 0
self._size = size
self._items = [None] * size
self._left = self._right = 0
self._cond = Condition()
def is_full(self):
with self._cond:
return self._left == (self._right + 1) % self._size
def is_empty(self):
with self._cond:
return self._left == self._right
def put(self, item):
with self._cond:
while self.is_full():
self._cond.wait()
self._right += 1
self._right %= self._size
self._items[self._right] = item
print 'put', item
self._cond.notify()
def get(self):
with self._cond:
while self.is_empty():
self._cond.wait()
self._left += 1
self._left %= self._size
item = self._items[self._left]
print 'get', item
self._cond.notify()
return item
SENTINEL = object()
def retrieve(buf):
while True:
item = buf.get()
if item is SENTINEL:
print 'Goodbye!'
break
some_buffer = Ringbuffer(5)
some_buffer.put(1)
some_buffer.put(2)
some_buffer.put(3)
t = Thread(target=lambda:retrieve(some_buffer))
t.start()
some_buffer.put('joe')
some_buffer.put(5)
some_buffer.put(6)
some_buffer.put(7)
some_buffer.put('jim')
some_buffer.put(SENTINEL)
Ein
Monitor ist, wie man sieht, nicht ein Objekt oder eine Funktion, es ist überhaupt
kein Ding, sondern ein abstraktes Konzept, das durch Conditions implementiert wird. Es ist ein
Design Pattern.
Man kann übrigens, wie DasIch vorgeschlagen hat, auch Dekoratoren verwenden:
Code: Alles auswählen
def locked_with(lock):
def decorated(method):
def decorator(self, *args, **kwargs):
with lock(self):
return method(self, *args, **kwargs)
return decorator
return decorated
def wait_if(is_in_state, get_cond):
def decorated(method):
def decorator(self, *args, **kwargs):
cond = get_cond(self)
with cond:
while is_in_state(self):
cond.wait()
result = method(self, *args, **kwargs)
cond.notify()
return result
return decorator
return decorated
class Ringbuffer(object):
def __init__(self, size):
assert size > 0
self._size = size
self._items = [None] * size
self._left = self._right = 0
self._cond = Condition()
def cond(self):
return self._cond
@locked_with(cond)
def is_full(self):
return self._left == (self._right + 1) % self._size
@locked_with(cond)
def is_empty(self):
return self._left == self._right
@wait_if(is_full, cond)
def put(self, item):
self._right += 1
self._right %= self._size
self._items[self._right] = item
print 'put', item
@wait_if(is_empty, cond)
def get(self):
self._left += 1
self._left %= self._size
item = self._items[self._left]
print 'get', item
return item
get_cond in wait_if() ist leider notwendig, damit man pro Klasse mehrere Conditions haben kann und nicht auf den Namen _cond festgelegt ist.
Wie Monitore funktionieren, und vieles mehr, steht in
diesem Buch.
Gruß,
Mick.
In specifications, Murphy's Law supersedes Ohm's.