Das Konzept *"concurrent programming"* ist eine Sammlung von Techniken, um innerhalb eines Programmes mehrere Aufgaben unabhängig -- oder fast unabhängig -- voneiner bearbeiten zu können. Auf entsprechenden Machinen, bzw. in einem Verbund von Computern, können diese Aufgaben wenn möglich auch gleichzeitig ausgeführt werden - das nennt sich *"parallel computing"*.
Eine der einfachsten Formen von concurrency sind Co-Routinen. Eine Co-Routine verhält sich hierbei ganz ähnlich zu einer Funktion, welche aber mehrmals hintereinander aufgerufen werden kann (wenn sie sich intern wiederholt). Zu jedem Aufruf gibt es ein dazugehöriges Ergebnis oder eine Exception, die andeutet dass die Co-Routine zu Ende ist.
Entscheidender Unterschied ist das yield
-Statement: Es kann an dieser Stelle ein Zwischenergebnis zurückgeben oder auf die Eingabe eines Wertes warten.
Verwendet werden Co-Routinen, indem sie zuerst Instanziert werden und dann entweder als Iterator zum Einsatz kommen oder explizit .next()
zum Abrufen des nächsten Wertes aufgerufen wird.
Wichtig ist zu verstehen, dass Co-Routinen nicht erst beim Aufrufen des .next()
calls zum Arbeiten beginnen.
Sie können selbständig im Hintergrund schon so lange beschäftigt sein,
bis sie an der Stelle des yield
-Statements ein fertiges Ergebnis zum Abruf bereit halten.
Hier einfache Beispiele:
def coroutine1():
yield "abc"
yield "xyz"
yield 777
c1 = coroutine1()
print(next(c1))
print(next(c1))
print(next(c1))
print(next(c1))
abc xyz 777
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-2-ac681be2fb60> in <module>() 3 print(next(c1)) 4 print(next(c1)) ----> 5 print(next(c1)) StopIteration:
def even_numbers(start_value = 0):
x = start_value
while True:
x += 1
if x % 2 == 0:
yield x
en = even_numbers(21)
for i in range(5):
print(next(en))
22 24 26 28 30
Da Co-Routinen eine spezielle Form von Iteratoren sind,
brechen sie in diesem Fall hier nicht von selbst ab.
Wir müssen daher in dieser for i in en2
Schleife sicherstellen,
dass irgendwann break
aufgerufen wird!
en2 = even_numbers(1)
for i in en2:
print(i)
if i > 10:
break
2 4 6 8 10 12
variable = yield
und coroutine_instance.send(arg)
erlauben,
Objekte an eine Co-Routine zu schicken.
Achtung, das Timing von next
und send
darf nicht aus dem Takt kommen!
def triple():
while True:
print(" Ready to recieve values")
y = yield
print(" I got %s" % y)
yield y * 3
t = triple()
next(t) # initialisierung der Co-Routine!
print("init fertig")
z = t.send(21)
print("habe %s erhalten" % z)
next(t)
z = t.send(-4)
print("habe %s erhalten" % z)
Ready to recieve values init fertig I got 21 habe 63 erhalten Ready to recieve values I got -4 habe -12 erhalten
Bonus: Verkettungen
def oddvals():
x = 1
while True:
yield x
x += 2
def sumall(co_other):
sum = 0
while True:
sum += next(co_other)
yield sum
a1 = oddvals()
sa = sumall(a1)
for i in range(10):
print(next(sa))
1 4 9 16 25 36 49 64 81 100
Kontrolle:
[sum(range(1, 2 * x, 2)) for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
from threading import Thread
from queue import Queue
from time import sleep, time
from random import random
t0 = time()
def calculate(name, input_queue, return_queue):
sum = 0
while True:
c = input_queue.get()
# simulate some work time
t = 1000. * (time() - t0)
sleep(random() * 0.1)
print("%6.0f[ms]: working in %s" % (t, name))
if c < 0:
print(" got -1, finishing up")
break
sum += c
return_queue.put(sum)
input_queue = Queue()
return_queue = Queue()
t1 = Thread(target=calculate, args=("T1", input_queue, return_queue))
t2 = Thread(target=calculate, args=(" T2", input_queue, return_queue))
t1.start()
t2.start()
for k in range(99999, 99999+30):
input_queue.put(k)
input_queue.put(-1)
input_queue.put(-1)
# wait for both threads to finish
# (otherwise they are discarded when main thread finishes)
t1.join()
t2.join()
5[ms]: working in T2 5[ms]: working in T1 94[ms]: working in T1 29[ms]: working in T2 111[ms]: working in T1 113[ms]: working in T2 195[ms]: working in T1 198[ms]: working in T1 196[ms]: working in T2 227[ms]: working in T1 271[ms]: working in T1 248[ms]: working in T2 313[ms]: working in T1 313[ms]: working in T2 371[ms]: working in T1 390[ms]: working in T2 426[ms]: working in T1 447[ms]: working in T2 493[ms]: working in T1 578[ms]: working in T1 544[ms]: working in T2 629[ms]: working in T2 678[ms]: working in T2 596[ms]: working in T1 692[ms]: working in T2 695[ms]: working in T1 741[ms]: working in T1 737[ms]: working in T2 805[ms]: working in T2 812[ms]: working in T2 got -1, finishing up 803[ms]: working in T1 894[ms]: working in T1 got -1, finishing up