Institute of Computer Science
  1. Courses
  2. 2023/24 spring
  3. Development of web services and distributed systems (LTAT.06.018)
ET
Log in

Development of web services and distributed systems 2023/24 spring

  • Pealeht
  • Loengud
  • Praktikumid
  • Lahenduste Esitamine

NB! Kui tekivad probleemid, soovite abi või vihjeid - siis küsige aine Slack kanalis (channel) #praktikum-1-lõimed

Esimene praktikum - Lõimede programmeerimine Pythonis

Hajussüsteemide sissejuhatuseks õpime ja tuletame meelde lõimedega programmeerimise kohta, kuna lõimede kasutamise juures kergivad esile mitmed sünkroniseerimise, eraldi töötavate osapoolde haldamise ja orkestreerimise probleemid, mis on omased hajusalt töötavates süsteemides. Lõimede teema on kaetud ka Objektorienteeritud aines, aga selles praktikumis kasutame lõimede programmeerimiseks Pythonit.

Selle praktikumi eesmärk on anda sissejuhatus lõimedepõhiste rakenduste loomisele Pythonis, kasutades erinevaid lõimehaldusmudeleid ja luues praktilise programmi, millega saab raamatuid alla tõmmata ning nende seest sõnede leidumist otsida.

Viited

  1. Lõimed Pythonis:
    • Python Dokumentatsioon: https://docs.python.org/3/library/threading.html
  2. PyCharm õpetus: https://www.jetbrains.com/help/pycharm/quick-start-guide.html
  3. Lõimed Javas (Objektorienteeritud programmeerimine aines): https://courses.cs.ut.ee/2022/OOP/spring/Main/Practice12

Ettevalmistavad tegevused

Võite valida endale sobiva Pythoni programmeerimiskeskkonna, aga soovitan teil kasutada PyCharm Python IDE'd, kuna see lihtsustab projektide haldust, virtuaalsete keskkondade ülesseadmist ning selle Professional versioon on kasutatav Tartu Ülikooli tudengitele tasuta.

Soovituslikuks Python versiooniks on Python 3.7, 3.8 või 3.9, kuna ülesannete lahendused on testitud selles versioonis. 3.12 Pythonis on mitmel tudengil probleeme tekkinud.

  • PyCharm Professional alla laadimine: https://www.jetbrains.com/pycharm/download/
  • PyCharm kasutamise (inglise keelne) õpetus: https://www.jetbrains.com/help/pycharm/quick-start-guide.html
  • PyCharm litsentside info: https://www.jetbrains.com/community/education/#students

Ülesanne 1.1: Raamatute alla laadimine Gutenberg repositooriumist

Kirjutame lihtsa ühelõimelise programmi raamatute alla laadimiseks ning salevstamiseks Gutenberg repositooriumist, kus on avatud litsentsiga ja tasuta raamatud kõigile kasutamiseks. Näiteks saab Sellel lehel nimekira juhuslikest raamatustest Gutenberg repositooriumis: https://www.gutenberg.org/ebooks/search/?sort_order=random

  • Loo funktsioonlae_alla_raamat(gutenberg_id):, mis saab sisendiks Gutenberg raamatu numbri(ID), ning laeb sellele vastava raamatu (.txt faili) alla ning salvestab selle arvutisse eraldi failina.
    • gutenberg_id on näiteks 45774
      • Ja sellele gutenberg_id'le vastav UTF8 tekstiformaadis raamatu aadress on:
        • https://www.gutenberg.org/cache/epub/45774/pg45774.txt
        • https://www.gutenberg.org/cache/epub/<gutenberg_id>/pg<gutenberg_id>.txt
    • Faile alla laadimiseks veebist saab kasutada näiteks response = requests.get() meetodit Python requests paketist. Ja response.text et tagastatava faili sisu "kätte saada" teksti kujul.
    • Faili saab salvestada endale sobivasse kausta. Näiteks PyCharm projekti alamkaustas raamatud
    • Faili salvestamisel saab kasutada lihtsat Python open() ja write() meetodeid, aga soovitatav on lisada encoding="utf-8" parameeter:
      •     f = open(file_name, 'w+', encoding="utf-8")
            f.write(content)
            f.close()
  • Kirjuta programmi põhimeetod, kus on list raamatute gutenberg_id'st ning kus on tsükkel, mis kutsub lae_alla_raamat funktsiooni välja iga listis oleva raamatu gutenberg_id peal.
    • Testi, et rakendus töötab vabalt valitud raamatute id'de korral.
      • Võimalik on kasutada ka vahemikke, näiteks gutenberg_id_list = range(100,120)
    • Näiteks:
      if __name__ == '__main__':
          gutenberg_id_list = range(100,120)
          for gutenberg_id in gutenberg_id_list: 
      
  • Mõõda, kui palju aega võtab 50 raamatu alla laadimine. Täpsus võiks olla millisekundites.
    • Saab kasutada näiteks Python time paketi meetodit time.time()

NB! Salvesta ülesande lahendus eraldi Python skriptina.

  • Soovitus:
    • Lõimede töö kontrollimiseks ja silumiseks võiks kasutada logimist.
    • Näide logimise üles seadmiseks, kus väljastatakse ka praegune aeg:
    • import logging
      logging.basicConfig(format="%(asctime)s - %(message)s", level=logging.INFO, datefmt="%H:%M:%S")
      
      logging.info("Programm: Enne loimede loomist")
      
      logging.info("Lõim alustab tööd")
      

Ülesanne 1.2: Lõimede loomine

Täiendame eelmise ülesande lahendust, käivitades kõik funktsiooni lae_alla_raamat() väljakutsed eraldi lõimes.

  • Kasuta paketi threading meetodit Thread() et luua üks lõim iga funktsiooni lae_alla_raamat() väljakutse kohta
    • Näide:
      x = threading.Thread(target=loime_funktsioon, args=(142,))
      x.start()
    • Selles näites on:
      • loime_funktsioon on funktsioon mida kutsutakse välja eraldi lõimena
      • 142 on funktsiooni loime_funktsioon argument. Argumendid on Python tuple andmestruktuuris ( ) komadega eraldatult
      • x.start() käivitab lõime
  • Mõõda, kui palju aega võtab nüüd 50 raamatu alla laadimine. Täpsus võiks olla millisekundites.
  • Küsimused:
    1. Mis toimub nüüd käivitusaja mõõtmisel?
      • Kas arvutatud aeg on korrektne? Miks?

Skripti käivitamisaega saab mõõta ka väljaspool skripti:

  • Windowsis saab kasutada (Kas PowerPoint käsureal või PyCharm terminalis) Measure-Command käsku.
    • Näide: Measure-Command { python ylesanne1.2.py }
  • Linuxis saab kasutada time käsku
    • Näide: time python ylesanne1.2.py
  • Küsimused:
    • Mis on selle ja eelmise ülesande lahenduse jooksutamise aja erinevus (50 raamatu alla laadimisel) sellisel viisil mõõtes?
    • Mis on teie arvates peamine põhjus või mõjutegur, miks lõimede põhine lahendus on aeglasem/kiirem?

NB! Salvesta ülesande lahendus eraldi Python skriptina.

Ülesanne 1.3: Lõimede grupid

Kui me soovima alla laadida sadu faile, siis on ebamõistlik kõikide jaoks individuaalne lõim sama aegselt tööle panna. Selle asemel on tihti mõistlik piirata lõimede maksimaalne arv ning kasutusele võtta lõimede grupid (Thread Pools)

  • Uurige kuidas kasutada Pythoni concurrent.futures.ThreadPoolExecutor klassi ning looge uus versioon programmist, mis kasutab maksimaalselt 10 lõime suurust lõimede gruppi.
    • Dokumentatioon ja lihtsad näited: https://docs.python.org/3/library/concurrent.futures.html
  • Mõõtke kui palju aega võtab ThreadPoolExecutor't kasutades 50 raamatu alla laadimine ja võrrelge tulemust eelmise kahe lahenduse tulemusega.
  • Soovitus: Kasutada ThreadPoolExecutor meetodit submit() lõimede loomiseks.

NB! Salvesta ülesande lahendus eraldi Python skriptina.

Ülesanne 1.4: Lõimede töö sünkroniseerimine

Eelmistes ülesannetes on meie lõimed olnud täiesti iseseisvad ning nende töö ei mõjutanud üksteist kuidagi. Selles ülesandes vaatame kuidas panna lõimed koostööd tegema selleks, et arvutada globaalset väärtust.

  • Teeme uue versiooni Pythoni programmist, mis laeb raamatud alla ning (selle asemel, et faile salvestada) otsib ja loendab mitu korda failis esineb mingi otsingu sõne. Näiteks "Estonia" või "language".
  • Terve programm peab väljastama kogu arvu, mitu korda selline sõne kõigis läbivaadatud raamatutes esines.
  • Loendurina kasutame globaalset muutujat, mida jagatakse lõimede vahel.
  • Lisaks peaksid lõimed globaalset muutujad jooksvalt uuendama, iga kord kui otsingu sõne leitakse mõnel raamatu real.
    • Lõim peaks raamatu sisu läbi vaatama rida-rea haaval.
    • Raamatuid enam failidesse salvestama ei pea.
    • Lõime funktsioon peab iga kord uuendama globaalse loenduri väärtust. (Mitte ainult üks kord lõime töö lõpus)
    • Programmi lõpus peaks välja printima otsingu sõne ning mitu korda see alla tõmmatud raamatutes leidus.

Vihjed:

  • Teksti jagamine ridadeks: for line in content.splitlines():
  • Globaalsete muutujate defineerimine:
    • def lae_alla_raamat_ja_otsi_sone(raamat, sone):
          global globaalne_summa
      ...
      
      if __name__ == '__main__':
          global globaalne_summa
          globaalne_summa = 0
      
  • Selleks et loendada kui mitu korda mõni sõna leidub pikemas tekstis/sõnes saab teksti jagada split() meetodi abil sõnade listiks ning kasutada pikem_sone.lower().split().count(otsingusone) meetodit selleks et leida mitu korda sõne sellises listis leidub.

NB! Salvesta ülesande lahendus eraldi Python skriptina.

Boonus ülesanne: Lõimede töö sünkroniseerimine jagatud objektide ja lukkudega

See on boonus ülesanne, mis ei ole kohustuslik, et praktikumi lahenduse eest täispunktid saada.

(Näita boonus ülesande kirjeldust)

Asendame globaalse muutuja MeieLoendur klassi objektiga, kus on lukk sisse ehitatud selleks, et kontrollida, et ainult üks lõim saab korraga loenduri väärtust muuta. See ei pruugi kõige kasulikum olla selle ülesande raames - lihtsamate loendurite puhul, aga kui samaaegselt on vaja mitut loendurit muuta, siis ei piisa lihtsast globaalsest muutujast.

Loome uue klassi MeieLoendur mis sisaldab meetodit loenduri väärtuse suurendamiseks ning lukku mis lubab ainult ühel lõimel korraga selle meetodi sees loenduri väärtust muuta.

  • class MeieLoendur:
        def __init__(self):
            self.loendur = 0
            self._lock = threading.Lock()
    
        def locked_update(self, count):
            with self._lock:
                self.loendur += count
    
        def print(self):
            if(self.loendur > 0):
                print("Praegune loenduri väärtus on: ", self.loendur)
  • Muutke programm ümber nii, et globaalse muutuja asmel kasutatakse globaalset MeieLoenduri objekti.
    • def lae_alla_raamat_ja_otsi_sone(raamat, sone):
          global loendur
          ...
      
      if __name__ == '__main__':
          global loendur
          loendur = MeieLoendur()
      
    • Loenduri väärtuse muutmine
      • loendur.locked_update(lisatav_vaartus)
    • Loenduri seisu printimine programmi lõpus: loendur.print()

NB! Salvesta ülesande lahendus eraldi Python skriptina.

Mittekohustuslik lisategevus:

  • Teeme eraldi lõime (ja funktsiooni), mis iga paari sekundi tagant prindib välja loenduri hetke väärtuse otsingu ajal.
  • Ja muul ajal jääb magama (time.sleep(x))

Lahenduse esitamine

Praktikumi lahendusena tuleb esitada:

  1. Python kood iga ülesande (1.1, 1.2, 1.3, 1.4) kohta.
  2. Programmi väljundi näide iga ülesande lahenduse kohta, kas tekstfailina (programmi väljundi logi) või ekraanipildina.
  3. Vastus ülesannete raames püstitatud küsimusetele (Küsimused on esile tõstetud sinise teksti abil)
  4. Failid tuleks kokku pakkida üheks Zip failiks enne üles laadimist.
  5. Boonus ülesande lahendamise korral:
    • Python kood
    • Programmi väljundi näide
You must be logged in and registered to the course in order to submit solutions.
  • Institute of Computer Science
  • Faculty of Science and Technology
  • University of Tartu
In case of technical problems or questions write to:

Contact the course organizers with the organizational and course content questions.
The proprietary copyrights of educational materials belong to the University of Tartu. The use of educational materials is permitted for the purposes and under the conditions provided for in the copyright law for the free use of a work. When using educational materials, the user is obligated to give credit to the author of the educational materials.
The use of educational materials for other purposes is allowed only with the prior written consent of the University of Tartu.
Terms of use for the Courses environment