In [1]:
%%javascript
$.getScript('http://kmahelona.github.io/ipython_notebook_goodies/ipython_notebook_toc.js')

'Współprogramy'

Typowym sposobem pisania programów komputerowych jest modularyzacja złożonego zadania obliczeniowego czyli jego podział na mniejsze, logicznie wyodrębnione jednostki. Jednostki takie przybierają w zależności od wybranego języka programowania formę funkcji, procedur albo klas. Po dekompozycji zadania obliczeniowego na moduły określa się sposób ich współpracy. Najczęściej koordynacje pracy modułów powierza się głównej funkcji sterującej (zwyczajowo nazywanej main). Funkcja sterująca okresla nastepstwo wykonania i zasady współpracy poszczególnych modułów przy użyciu (poznanych już) instrukcji warunkowych, petli itd. W takim podejściu moduły przyjęło się nazywać podprogramami (ang. subroutine).

Istnieje alternatywny sposób koordynacji pracy podzadań obliczeniowych bez użycia funkcji sterującej, w którym przebieg przetwarzania wynika wyłącznie z komunikacji między modułami. Przepływ sterowania w tym podejściu określa się przez łączenie zadań w łańcuchy (potoki). Takie podejście to nazywa się też czasami sterowaniem przez zdarzenia (ang. event driven). W takim podejściu moduły przyjęło się nazywać współprogramami (ang. coroutine). Jak się przekonamy podejście oparte o współprogramy doskonale pasuje do modelowania złożonych systemów kolejkowych.

generatory - krótkie przypomnienie

W pythonie współprogramy tworzymy w sposób taki jak generatory. W istocie okazuje się, że generatory Pythona są szczególnym typem współprogramów.

In [ ]:
g = [x**2 for x in range(10)]
In [ ]:
g
In [ ]:
type(g)
In [ ]:
g = (x**2 for x in range(10))
In [ ]:
g
In [ ]:
type(g)
In [ ]:
list(g)
In [ ]:
g = (x**2 for x in range(10))
g
In [ ]:
next(g)
In [ ]:
g.send(1)
In [ ]:
def moj_generator():
    n = 0
    print('przed yield: n =', n)
    while True:
        n += 2
        yield n
        print('po yield: n =', n)
In [ ]:
type(moj_generator)
In [ ]:
g = moj_generator()
In [ ]:
g
In [ ]:
next(g)

przykład prostego 'koprogramu':

In [ ]:
def my_coroutine():
    n = 0
    print('przed yield: n =', n)
    while True:
        n = yield n+2
        print('po yield: n =', n)
In [ ]:
g = my_coroutine()
In [ ]:
g

przed pierwszym użyciem 'współprogram' trzeba zainicjować poleceniem next albo metodą send(None)

In [ ]:
next(g)

wartosci do coroutinem przekazujemy wywołując metodę send(wartość)

In [ ]:
g.send(50)

wzorzec produkuj-filtruj-konsumuj

Typowym sposobem wykorzystania potoku przetwarzania jest wzorzec 'producent-konsument', w którym współprogram pełni jedną z trzech ról:

  • producenta, który generuje dane wejściowe dla potoku korzystając z medody send()
  • filtra, który konsumuje i wstepnie przetwarza (np. filtruje, klasyfikuje itp.) dane wejsciowe, nastepnie przesyła je do przetwarzania na kolejnych etapach potoku korzystając z metod yield() i send()
  • konsumenta, który dokonuje końcowego przetwarzania danych otrzymanych z wykorzystaniem metody yield() i przekazuje je na wyjście potoku Przyjrzyjmy się jak wzorzec 'producent-konsument' możemy wykorzytać do implementacji prostego systemu kolejkowego omawianego na poprzednich zajęciach.
In [ ]:
import random as rnd
LAMBDA = 1

# konsument - odbiorca zgłoszeń
def consumer():
    start_time = 0
    end_time = 0
    while True:
        arrival_time = yield start_time, end_time
        start_time = max(arrival_time, end_time)
        service_time = rnd.expovariate(LAMBDA)
        end_time = start_time + service_time
In [ ]:
# tworzymy i inicjujemy serwer
server = consumer()
next(server)
In [ ]:
server.send(0)
In [ ]:
# producent - generator zgłoszeń
def producer(N, server):
    arrival_time = 0
    print('arrival_time \tstart_time \tend_time \twait_time')
    for i in range(N):
        result = server.send(arrival_time)
        print('%05f \t%05f \t%05f \t%05f' % (arrival_time, result[0], result[1], result[0]-arrival_time))
        arrival_time += rnd.expovariate(LAMBDA*3)
In [ ]:
# tworzymy i inicjujemy serwer
server = consumer()
In [ ]:
server
In [ ]:
next(server)
In [ ]:
producer, consumer, server

symulacja jest uruchamiana i sterowana przez strumień zgłoszeń generowany przez producenta

In [ ]:
producer(20,server)