Ohjelmoinnin peruskurssi Y2, kurssimateriaali

5.3. Pythonin säikeet

«  5.2. Rinnakkainen suoritus   ::   Etusivulle   ::   5.4. Tehtävä: LZW-pakkaus  »

5.3. Pythonin säikeet

Tässä luvussa perehdymme Pythonin säikeisiin. Lähdemateriaalina on:

Huom!

Pythonissa oli aiemmin käytössä moduli thread, joka toteutti yksinkertaisemman säiemekanismin. Tähän voi törmätä vanhemmissa tietolähteissä, mutta sitä ei enää käytetä.

Luokka threading.Thread

Luokka threading.Thread esittää yksittäisiä Pythonin säikeitä.

Säikeen voi luoda kahdella tapaa, joko antamalla parametrina callable-olion (esim. funktion) tai määrittämällä uudelleen metodin Thread.run. Esimerkki edellisestä tavasta:

thread = threading.Thread(target=aja_kertoma, args=(number,))

Tässä luodaan säie, joka käynnistyessään suorittaa funktionkutsun aja_kertoma(number).

Välikysymys:

Osaatko sanoa, miksi yllä kirjoitettiin args=(number,) eikä args=(number)?

Luokan Thread voi periä, mutta ainoastaan luontimetodin __init__ sekä metodin run saa määrittää uudestaan. Muuten voi seurata sotkua.

Luokassa on määritetty seuraavat hyödylliset metodit:

  • start(): käynnistää säikeen

  • run(): säikeen varsinainen toiminta. Tämän voi siis määrittää uudestaan; muussa tapauksessa metodi kutsuu parametrina annettua target oliota, joka tyypillisesti on funktio. Säikeen suoritus loppuu, kun run-metodista poistutaan.

  • join(timeout=None): Tätä metodia kutsuva säie odottaa, kunnes säie, johon kutsu kohdistuu päättyy. Jos timeout on annettu, on sen oltava None tai maksimiodotusajan sekunteina sisältävä liukuluku. Metodin paluuarvosta ei selviä, loppuiko säikeen suoritus oikeasti, joten tarvittaessa on käytettävä metodia is_alive tämän selvittämiseen.

    Metodi join ei siis lopeta säikeen suoritusta.

  • is_alive(): onko säie hengissä? Arvo on tosi metodin run() suorituksen ajan (tai oikeastaan alkaen hiukan ennen sen suoritusta ja päättyen hiukan sen suorituksen jälkeen).

Säikeen nimen saa attribuutista name.

Esimerkki: Oman säieluokan tekeminen

#!/usr/bin/env python3
import time
import threading

class Tulostaja(threading.Thread):
    def __init__(self, nimi):
        super().__init__()
        self.nimi = nimi

    def run(self):
        for i in range(10):
            time.sleep(0.5)
            print('{}: viesti {}'.format(self.nimi, i))

def main():
    saieA = Tulostaja('Eka')
    saieB = Tulostaja('Toka')

    saieA.start()
    saieB.start()

    for i in range(10):
        time.sleep(1)
        print('{}, kierros {}'.format("main", i))

if __name__ =='__main__':
    main()

Ajettaessa saadaan tulostus:

Eka: viesti 0
Toka: viesti 0
main, kierros 0
Eka: viesti 1
Toka: viesti 1
Eka: viesti 2
Toka: viesti 2
main, kierros 1
Eka: viesti 3
Toka: viesti 3
Eka: viesti 4
Toka: viesti 4
main, kierros 2
Eka: viesti 5
Toka: viesti 5
Eka: viesti 6
Toka: viesti 6
main, kierros 3
Toka: viesti 7
Eka: viesti 7
Toka: viesti 8
Eka: viesti 8
main, kierros 4
Toka: viesti 9
Eka: viesti 9
main, kierros 5
main, kierros 6
main, kierros 7
main, kierros 8
main, kierros 9

Lukot

Edellisessä esimerkissä säikeet toimivat näppärästi toisistaan riippumatta ja tulostivat siististi viestinsä. Tarkastellaan seuraavaa hieman muutettua esimerkkiä, jossa kaikki ei sujukaan enää nätisti.

Tulostukset sekaisin

#!/usr/bin/env python3
import time
import threading
import random

class Tulostaja(threading.Thread):
    def __init__(self, nimi):
        super().__init__()
        self.nimi = nimi

    def run(self):
        for i in range(10):
            time.sleep(0.5)
            self.tulostaViesti(i)

    def raskasLasku(self, n):
        # Mielivaltainen aikaa vievä toimitus
        sum = 0
        for k in range(random.randint(0, 2**min(20, 4*n))):
            sum += k
        return sum

    def tulostaViesti(self, i):
        print('{}: viesti {} alkaa'.format(self.nimi, i))
        for j in range(i):
            tulos = self.raskasLasku(i)
            print('{}: viesti {} osa {}: {}'.format(self.nimi, i, j, tulos))
        print('{}: viesti {} loppuu'.format(self.nimi, i))

def main():
    saieA = Tulostaja('Eka')
    saieB = Tulostaja('Toka')

    saieA.start()
    saieB.start()

if __name__ =='__main__':
    main()

Tämä tulostaa:

Eka: viesti 0 alkaa
Eka: viesti 0 loppuu
Toka: viesti 0 alkaa
Toka: viesti 0 loppuu
Eka: viesti 1 alkaa
Eka: viesti 1 osa 0: 36
Eka: viesti 1 loppuu
Toka: viesti 1 alkaa
Toka: viesti 1 osa 0: 36
Toka: viesti 1 loppuu
Eka: viesti 2 alkaa
Eka: viesti 2 osa 0: 11026
Eka: viesti 2 osa 1: 17020
Eka: viesti 2 loppuu
Toka: viesti 2 alkaa
Toka: viesti 2 osa 0: 16836
Toka: viesti 2 osa 1: 21736
Toka: viesti 2 loppuu
Toka: viesti 3 alkaa
Toka: viesti 3 osa 0: 946
Toka: viesti 3 osa 1: 42195
Toka: viesti 3 osa 2: 127765
Toka: viesti 3 loppuu
Eka: viesti 3 alkaa
Eka: viesti 3 osa 0: 2543640
Eka: viesti 3 osa 1: 4928230
Eka: viesti 3 osa 2: 4846941
Eka: viesti 3 loppuu
Toka: viesti 4 alkaa
Toka: viesti 4 osa 0: 531298503
Toka: viesti 4 osa 1: 222699960
Toka: viesti 4 osa 2: 63782865
Eka: viesti 4 alkaa
Eka: viesti 4 osa 0: 428849541
Eka: viesti 4 osa 1: 299158030
Eka: viesti 4 osa 2: 172766166
Eka: viesti 4 osa 3: 32124120
Eka: viesti 4 loppuu
Toka: viesti 4 osa 3: 1508350350
Toka: viesti 4 loppuu
Eka: viesti 5 alkaa
Toka: viesti 5 alkaa
Toka: viesti 5 osa 0: 17495441211
Toka: viesti 5 osa 1: 57451177878
Eka: viesti 5 osa 0: 258554202753
Eka: viesti 5 osa 1: 83696746953
Toka: viesti 5 osa 2: 403054292530
Eka: viesti 5 osa 2: 308705852890
Toka: viesti 5 osa 3: 538891726366
Toka: viesti 5 osa 4: 44228287236
Toka: viesti 5 loppuu
Eka: viesti 5 osa 3: 481286944386
Eka: viesti 5 osa 4: 10227002653
Eka: viesti 5 loppuu
Toka: viesti 6 alkaa
Eka: viesti 6 alkaa
Toka: viesti 6 osa 0: 271498646286
Eka: viesti 6 osa 0: 81622746666
Eka: viesti 6 osa 1: 356571124003
Toka: viesti 6 osa 1: 501646853835
Eka: viesti 6 osa 2: 256656391111
Toka: viesti 6 osa 2: 217530473436
Eka: viesti 6 osa 3: 234467644866
Eka: viesti 6 osa 4: 13995230556
Toka: viesti 6 osa 3: 542310613426
Eka: viesti 6 osa 5: 65879599591
Eka: viesti 6 loppuu
Toka: viesti 6 osa 4: 384692114085
Toka: viesti 6 osa 5: 392672118306
Toka: viesti 6 loppuu
Eka: viesti 7 alkaa
Eka: viesti 7 osa 0: 362910679176
Eka: viesti 7 osa 1: 50249413620
Toka: viesti 7 alkaa
Eka: viesti 7 osa 2: 302031966720
Eka: viesti 7 osa 3: 6953447628
Toka: viesti 7 osa 0: 431827589115
Eka: viesti 7 osa 4: 240989101381
Eka: viesti 7 osa 5: 3477571503
Eka: viesti 7 osa 6: 28090069776
Eka: viesti 7 loppuu
Toka: viesti 7 osa 1: 406143355815
Toka: viesti 7 osa 2: 94692774520
Toka: viesti 7 osa 3: 151325634453
Toka: viesti 7 osa 4: 113831528370
Toka: viesti 7 osa 5: 29178877951
Toka: viesti 7 osa 6: 72425325528
Toka: viesti 7 loppuu
Eka: viesti 8 alkaa
Eka: viesti 8 osa 0: 42919372653
Eka: viesti 8 osa 1: 3678203565
Eka: viesti 8 osa 2: 102650484651
Eka: viesti 8 osa 3: 405952309153
Eka: viesti 8 osa 4: 28350448140
Toka: viesti 8 alkaa
Toka: viesti 8 osa 0: 20418264240
Eka: viesti 8 osa 5: 233941338190
Toka: viesti 8 osa 1: 46714170630
Eka: viesti 8 osa 6: 200083472578
Eka: viesti 8 osa 7: 642199041
Eka: viesti 8 loppuu
Toka: viesti 8 osa 2: 301638046986
Toka: viesti 8 osa 3: 40168691641
Toka: viesti 8 osa 4: 7704066385
Toka: viesti 8 osa 5: 364656719001
Toka: viesti 8 osa 6: 100316530
Toka: viesti 8 osa 7: 375818056965
Toka: viesti 8 loppuu
Eka: viesti 9 alkaa
Eka: viesti 9 osa 0: 467945407405
Eka: viesti 9 osa 1: 25036842106
Eka: viesti 9 osa 2: 4584845161
Eka: viesti 9 osa 3: 269246263110
Eka: viesti 9 osa 4: 1484062440
Eka: viesti 9 osa 5: 66147028503
Toka: viesti 9 alkaa
Toka: viesti 9 osa 0: 120966326911
Toka: viesti 9 osa 1: 9729915751
Eka: viesti 9 osa 6: 449605625778
Eka: viesti 9 osa 7: 111872304636
Toka: viesti 9 osa 2: 344763396253
Eka: viesti 9 osa 8: 43205211946
Eka: viesti 9 loppuu
Toka: viesti 9 osa 3: 508579995696
Toka: viesti 9 osa 4: 389784899778
Toka: viesti 9 osa 5: 49765865841
Toka: viesti 9 osa 6: 232995612930
Toka: viesti 9 osa 7: 93810977281
Toka: viesti 9 osa 8: 142801566571
Toka: viesti 9 loppuu

Mitä tapahtui? Ensimmäiset viestit tulivat nätisti kokonaisina, mutta Tokan viesti 4 ei enää tullutkaan vaan Ekan viesti putkahti väliin. Syy tähän on että metodi raskasLasku alkoi isommilla parametrin arvoilla viedä pitemmän aikaa ja viestin osien tulostuksen välit tulivat niin pitkiksi, että Python ehti vaihtaa suoritettavaa säiettä.

Tulostukset taas järjestyksessä

Yksinkertainen korjaus on käyttää tulostamisen ympärillä lukkoa. Seuraavassa teemme sen Pythonin with -lausetta käyttäen.
#!/usr/bin/env python3
import time
import threading
import random

tulostusLukko = threading.Lock()

class Tulostaja(threading.Thread):
    def __init__(self, nimi):
        super().__init__()
        self.nimi = nimi

    def run(self):
        for i in range(10):
            time.sleep(0.5)
            self.tulostaViesti(i)

    def raskasLasku(self, n):
        # Mielivaltainen aikaa vievä toimitus
        sum = 0
        for k in range(random.randint(0, 2**min(20, 4*n))):
            sum += k
        return sum

    def tulostaViesti(self, i):
        with tulostusLukko:
            print('{}: viesti {} alkaa'.format(self.nimi, i))
            for j in range(i):
                tulos = self.raskasLasku(i)
                print('{}: viesti {} osa {}: {}'.format(self.nimi, i, j, tulos))
            print('{}: viesti {} loppuu'.format(self.nimi, i))

def main():
    saieA = Tulostaja('Eka')
    saieB = Tulostaja('Toka')


    saieA.start()
    saieB.start()


if __name__ =='__main__':
    main()

Nyt saamme taas siistin tulostuksen:

Eka: viesti 0 alkaa
Eka: viesti 0 loppuu
Toka: viesti 0 alkaa
Toka: viesti 0 loppuu
Eka: viesti 1 alkaa
Eka: viesti 1 osa 0: 10
Eka: viesti 1 loppuu
Toka: viesti 1 alkaa
Toka: viesti 1 osa 0: 1
Toka: viesti 1 loppuu
Eka: viesti 2 alkaa
Eka: viesti 2 osa 0: 3
Eka: viesti 2 osa 1: 253
Eka: viesti 2 loppuu
Toka: viesti 2 alkaa
Toka: viesti 2 osa 0: 171
Toka: viesti 2 osa 1: 13861
Toka: viesti 2 loppuu
Eka: viesti 3 alkaa
Eka: viesti 3 osa 0: 220780
Eka: viesti 3 osa 1: 3073960
Eka: viesti 3 osa 2: 1648020
Eka: viesti 3 loppuu
Toka: viesti 3 alkaa
Toka: viesti 3 osa 0: 993345
Toka: viesti 3 osa 1: 692076
Toka: viesti 3 osa 2: 2137278
Toka: viesti 3 loppuu
Eka: viesti 4 alkaa
Eka: viesti 4 osa 0: 1557071110
Eka: viesti 4 osa 1: 1126391916
Eka: viesti 4 osa 2: 555994531
Eka: viesti 4 osa 3: 27966
Eka: viesti 4 loppuu
Toka: viesti 4 alkaa
Toka: viesti 4 osa 0: 582240750
Toka: viesti 4 osa 1: 53690703
Toka: viesti 4 osa 2: 35099631
Toka: viesti 4 osa 3: 136727916
Toka: viesti 4 loppuu
Eka: viesti 5 alkaa
Eka: viesti 5 osa 0: 269439292486
Eka: viesti 5 osa 1: 72155736786
Eka: viesti 5 osa 2: 202769537790
Eka: viesti 5 osa 3: 443482789366
Eka: viesti 5 osa 4: 11134871065
Eka: viesti 5 loppuu
Toka: viesti 5 alkaa
Toka: viesti 5 osa 0: 168787762578
Toka: viesti 5 osa 1: 440794302985
Toka: viesti 5 osa 2: 335013279076
Toka: viesti 5 osa 3: 1030784310
Toka: viesti 5 osa 4: 478985079420
Toka: viesti 5 loppuu
Eka: viesti 6 alkaa
Eka: viesti 6 osa 0: 22866659731
Eka: viesti 6 osa 1: 153044164378
Eka: viesti 6 osa 2: 484906164445
Eka: viesti 6 osa 3: 503257789378
Eka: viesti 6 osa 4: 241407218976
Eka: viesti 6 osa 5: 426452445156
Eka: viesti 6 loppuu
Toka: viesti 6 alkaa
Toka: viesti 6 osa 0: 120837000606
Toka: viesti 6 osa 1: 232901418753
Toka: viesti 6 osa 2: 198460395136
Toka: viesti 6 osa 3: 19079932185
Toka: viesti 6 osa 4: 63117801456
Toka: viesti 6 osa 5: 8244371436
Toka: viesti 6 loppuu
Eka: viesti 7 alkaa
Eka: viesti 7 osa 0: 143530372090
Eka: viesti 7 osa 1: 198570033145
Eka: viesti 7 osa 2: 116765039625
Eka: viesti 7 osa 3: 152919154851
Eka: viesti 7 osa 4: 112353879561
Eka: viesti 7 osa 5: 255110459253
Eka: viesti 7 osa 6: 224173040491
Eka: viesti 7 loppuu
Toka: viesti 7 alkaa
Toka: viesti 7 osa 0: 21743110311
Toka: viesti 7 osa 1: 139327327503
Toka: viesti 7 osa 2: 196379891865
Toka: viesti 7 osa 3: 10106225535
Toka: viesti 7 osa 4: 549433424046
Toka: viesti 7 osa 5: 515159698581
Toka: viesti 7 osa 6: 478735528260
Toka: viesti 7 loppuu
Eka: viesti 8 alkaa
Eka: viesti 8 osa 0: 365407770003
Eka: viesti 8 osa 1: 101047376475
Eka: viesti 8 osa 2: 369035425605
Eka: viesti 8 osa 3: 281095896115
Eka: viesti 8 osa 4: 79074890040
Eka: viesti 8 osa 5: 28910385570
Eka: viesti 8 osa 6: 106325741511
Eka: viesti 8 osa 7: 402239470128
Eka: viesti 8 loppuu
Toka: viesti 8 alkaa
Toka: viesti 8 osa 0: 61430557870
Toka: viesti 8 osa 1: 1057885003
Toka: viesti 8 osa 2: 549432375778
Toka: viesti 8 osa 3: 263479560240
Toka: viesti 8 osa 4: 113340604105
Toka: viesti 8 osa 5: 299961914878
Toka: viesti 8 osa 6: 524271104136
Toka: viesti 8 osa 7: 8371209528
Toka: viesti 8 loppuu
Eka: viesti 9 alkaa
Eka: viesti 9 osa 0: 348825039885
Eka: viesti 9 osa 1: 102659546881
Eka: viesti 9 osa 2: 377857327221
Eka: viesti 9 osa 3: 94548348378
Eka: viesti 9 osa 4: 137480113161
Eka: viesti 9 osa 5: 150796315725
Eka: viesti 9 osa 6: 69799875265
Eka: viesti 9 osa 7: 11175648256
Eka: viesti 9 osa 8: 60448166551
Eka: viesti 9 loppuu
Toka: viesti 9 alkaa
Toka: viesti 9 osa 0: 193303629753
Toka: viesti 9 osa 1: 307235886903
Toka: viesti 9 osa 2: 350897870778
Toka: viesti 9 osa 3: 157308050871
Toka: viesti 9 osa 4: 293153222071
Toka: viesti 9 osa 5: 88162414005
Toka: viesti 9 osa 6: 128547569535
Toka: viesti 9 osa 7: 44789496753
Toka: viesti 9 osa 8: 18068339656
Toka: viesti 9 loppuu

Ehtomuuttujat

Seuraavassa tarkastellaan tuottaja-kuluttaja -ongelmaa. Tuottajat valmistavat tuotteita, joita kuluttajat kuluttavat. Tuottajien ja kuluttajien välissä on rajallisen kokoinen puskuri, jota käytetään välivarastona tuotetuille mutta vielä kuluttamattomille tuotteille. Kuluttaja ei voi kuluttaa tuotteita, jos niitä ei ole tarjolla puskurissa ja vastaavasti tuottaja ei voi tuottaa lisää tuotteita, jos puskuri on jo täynnä.

Puskuri voidaan määrittää esimerkiksi seuraavasti:

class Buffer:
    def __init__(self, capacity):
        self.capacity = capacity
        self.contents = []

    def isFull(self):
        return len(self.contents) == self.capacity

    def isEmpty(self):
        return len(self.contents) == 0

    def addItem(self, item):
        if self.isFull():
            raise BufferException("Buffer is already full")
        else:
            self.contents.append(item)

    def removeItem(self):
        if self.isEmpty():
            raise BufferException("Buffer is empty")
        else:
            item = self.contents.pop(0)
            return item

    def print(self):
        print("Buffer has {} items:".format(len(self.contents)))
        for item in self.contents:
            print("  {}".format(item))
        print()

Tuottajat ja kuluttajat on kätevää esittää säikeinä, jolloin ne voivat toimia rinnakkain. Ensimmäinen ongelma on, miten pidetään huolta siitä, että puskuria käytetään hallitusti eivätkä säikeet sotke sen sisältöä yrittämällä päivittää sitä samanaikaisesti. Tämän voisi helposti ratkaista käyttämällä lukkoa, joka pitää saada haltuun ennen kuin saa päivittää puskuria. Nyt on kuitenkin toinen ongelma: mitä jos tuottaja on saanut lukon haltuunsa ja yrittää päivittää puskuria, mutta se on jo täynnä? Tuottajan lienee syytä vapauttaa lukko, mutta milloin sen on mielekästä yrittää uudestaan päivitystä?

Tämän ongelman ratkaisemiseen käytetään niin sanottuja ehtomuuttujia (engl. condition variable). Ehtomuuttujaan liittyy lukon lisäksi operaatiot wait ja notify. Jos tuottajasäie ei voi päivittää puskuria, koska se on täysi, kutsuu se ehtomuuttujan metodia wait ja jää odottamaan. Tämä odottaminen ei syö järjestelmän resursseja, koska odottava säie on passiivisena ehtomuuttujaan liittyvässä jonossa. Kun joku kuluttajasäie on poistanut puskurista tuotteen, kutsuu se vastaavasti ehtomuuttujan metodia notify. Tällöin Python ottaa jonkun wait -kutsulla odottamaan siirtyneen säikeen ja antaa sen jatkaa suoritusta. Samalla tämä säie saa haltuunsa ehtomuuttujaan liittyvän lukon, josta se automaattisesti luopui kutsuessaan metodia wait.

Seuraavassa tämä on koottu kolmeen luokkaan, Producer, Consumer sekä Factory, joka kokoaa yhteen joukon tuottajia ja kuluttajia sekä puskurin ja tarvittavan ehtomuuttujan. Tuottaja suorittaa metodissa run silmukkaa, jossa tuotetaan uusi tuote metodilla produceItem. Tämä jatkuu, kunnes tuo metodi heittää poikkeuksen FactoryStopped. Metodissa produceItem aluksi tehdään tuote (tässä käytetään metodia time.sleep(), jotta saadaan näennäisesti aikaa kulumaan tuotteen tekemiseen). Sitten with -lauseen avulla otetaan ehtomuuttujaan liittyvä lukko haltuun ja tarkistetaan while-silmukassa onko puskuri täysi. Jos se on, kutsutaan metodia wait, jolloin lukko vapautuu ja säie jää odottamaan. Odottamista ei kuitenkaan tehdä, jos tehdas ei enää ole käynnissä (eli factory.isRunning on False). Odottaminen päättyy, kun joku toinen säie kutsuu ehtomuuttujan metodia notify ja Python valitsee tuottajan suoritukseen. While-silmukassa tarkistetaan uudestaan, onko puskurissa nyt tilaa ja jos on (tässä tapauksessa notify-metodia oli kutsunut joku kuluttaja) ja tehdas on yhä käynnissä, lisätään tuote puskuriin. Jos tilaa ei vieläkään ole (tässä tapauksessa notify-metodia oli kutsunut joku tuottaja) jatketaan odottamista. Lopuksi kutsutaan ehtomuuttujan metodia notify. Tällöin mahdollisesti odottava toinen tuottaja tai kuluttaja pääsee jatkamaan.

Kuluttaja toimii samankaltaisesti, mutta silmukkaehtona on että puskuri ei ole tyhjä. Tehtaan pysähtyminen tarkastetaan aina silmukkaehdossa ja jos silmukasta poistuttaessa tehdas on pysähtynyt, heitetään poikkeus FactoryStoppedException.

class Producer(threading.Thread):
    def __init__(self, name, factory, productionDelay):
        super().__init__()
        self.name = name
        self.factory = factory
        self.productionDelay = productionDelay
        self.itemCount = 0

    def produceItem(self):
        time.sleep(self.productionDelay)
        item = "<item {} by {}>".format(self.itemCount, self.name)
        self.itemCount += 1
        buffer = self.factory.buffer
        cv = self.factory.conditionVariable
        with cv:
            while buffer.isFull() and factory.isRunning:
                cv.wait()
            if factory.isRunning:
                print("Adding {} to buffer".format(item))
                buffer.addItem(item)
                buffer.print()
                cv.notify()
            else:
                raise FactoryStoppedException

    def run(self):
        try:
            while True:
                self.produceItem()
        except FactoryStoppedException:
            return

class Consumer(threading.Thread):
    def __init__(self, name, factory, consumptionDelay):
        super().__init__()
        self.name = name
        self.factory = factory
        self.consumptionDelay = consumptionDelay

    def consumeItem(self):
        buffer = self.factory.buffer
        cv = self.factory.conditionVariable
        with cv:
            while buffer.isEmpty() and factory.isRunning:
                cv.wait()
            if factory.isRunning:
                item = buffer.removeItem()
                print("Consumer {} removed {} from buffer".format(self.name, item))
                buffer.print()
                cv.notify()
            else:
                raise FactoryStoppedException
        time.sleep(self.consumptionDelay)

    def run(self):
        try:
            while True:
                self.consumeItem()
        except FactoryStoppedException:
            return

class Factory(threading.Thread):
    def __init__(self, bufferCapacity, producerDefinitions, consumerDefinitions, runTime):
        super().__init__()
        self.buffer = Buffer(bufferCapacity)
        self.conditionVariable = threading.Condition()
        self.producers = [ Producer(name, self, delay) for name, delay in producerDefinitions ]
        self.consumers = [ Consumer(name, self, delay) for name, delay in consumerDefinitions ]
        self.runTime = runTime
        self.isRunning = True

    def run(self):
        self.running = True
        for thread in self.consumers + self.producers:
            thread.start()
        time.sleep(self.runTime)
        with self.conditionVariable:
            self.isRunning = False
            print("Stopping factory")
            self.buffer.print()
            self.conditionVariable.notify_all()
        for thread in self.consumers + self.producers:
            thread.join()

Tehdään vielä pääohjelma, jossa luodaan tehdas

if __name__ == "__main__":
    factory = Factory(4, [ ("p1", 1), ("p2", 1), ("p3", 1), ("p4", 1) ], [ ("c", 3) ], 20)
    factory.start()

Koko ohjelma on tiedostossa ../_static/example-files/k05/producer_consumer.py.

Kun se suoritetaan, saadaan seuraava tulos:

Adding <item 0 by p1> to buffer
Buffer has 1 items:
  <item 0 by p1>

Adding <item 0 by p2> to buffer
Buffer has 2 items:
  <item 0 by p1>
  <item 0 by p2>

Adding <item 0 by p3> to buffer
Buffer has 3 items:
  <item 0 by p1>
  <item 0 by p2>
  <item 0 by p3>

Adding <item 0 by p4> to buffer
Buffer has 4 items:
  <item 0 by p1>
  <item 0 by p2>
  <item 0 by p3>
  <item 0 by p4>

Consumer c removed <item 0 by p1> from buffer
Buffer has 3 items:
  <item 0 by p2>
  <item 0 by p3>
  <item 0 by p4>

Adding <item 1 by p1> to buffer
Buffer has 4 items:
  <item 0 by p2>
  <item 0 by p3>
  <item 0 by p4>
  <item 1 by p1>

Consumer c removed <item 0 by p2> from buffer
Buffer has 3 items:
  <item 0 by p3>
  <item 0 by p4>
  <item 1 by p1>

Adding <item 1 by p2> to buffer
Buffer has 4 items:
  <item 0 by p3>
  <item 0 by p4>
  <item 1 by p1>
  <item 1 by p2>

Consumer c removed <item 0 by p3> from buffer
Buffer has 3 items:
  <item 0 by p4>
  <item 1 by p1>
  <item 1 by p2>

Adding <item 1 by p4> to buffer
Buffer has 4 items:
  <item 0 by p4>
  <item 1 by p1>
  <item 1 by p2>
  <item 1 by p4>

Consumer c removed <item 0 by p4> from buffer
Buffer has 3 items:
  <item 1 by p1>
  <item 1 by p2>
  <item 1 by p4>

Adding <item 1 by p3> to buffer
Buffer has 4 items:
  <item 1 by p1>
  <item 1 by p2>
  <item 1 by p4>
  <item 1 by p3>

Consumer c removed <item 1 by p1> from buffer
Buffer has 3 items:
  <item 1 by p2>
  <item 1 by p4>
  <item 1 by p3>

Adding <item 2 by p1> to buffer
Buffer has 4 items:
  <item 1 by p2>
  <item 1 by p4>
  <item 1 by p3>
  <item 2 by p1>

Consumer c removed <item 1 by p2> from buffer
Buffer has 3 items:
  <item 1 by p4>
  <item 1 by p3>
  <item 2 by p1>

Adding <item 2 by p2> to buffer
Buffer has 4 items:
  <item 1 by p4>
  <item 1 by p3>
  <item 2 by p1>
  <item 2 by p2>

Consumer c removed <item 1 by p4> from buffer
Buffer has 3 items:
  <item 1 by p3>
  <item 2 by p1>
  <item 2 by p2>

Adding <item 2 by p4> to buffer
Buffer has 4 items:
  <item 1 by p3>
  <item 2 by p1>
  <item 2 by p2>
  <item 2 by p4>

Stopping factory
Buffer has 4 items:
  <item 1 by p3>
  <item 2 by p1>
  <item 2 by p2>
  <item 2 by p4>

Palaute

«  5.2. Rinnakkainen suoritus   ::   Etusivulle   ::   5.4. Tehtävä: LZW-pakkaus  »