Knjiga - mrežno i paralelno programiranje

11. Modul multithreading

27.5.2020.

Prema Mooreovom zakonu (1965.), broj tranzistora koji se ugrađuju u procesor se udvostručuje svake godine. Problem je zagrijavanje tranzistora. Rješenje je ugradnja više procesora, odnosno jezgri u računalo. Kod više procesora svaki ima primjerice svoj cache dok kod višejezgrenih sve jezgre koriste zajednički cache.

Pokretanjem programa on se smješta u RAM, dio RAM-a alocira se i za podatke s kojima će program raditi (varijable). Program smješten u RAM ima i neka dodatna obilježja: vrijeme početka rada, vrijeme završetka,… te takav program zovemo proces.

Na istovremeno može biti pokrenuto više procesa. Procesor najčešće procese obrađuje kružnom podjelom vremena (Round Robin) koja se temelji na podjeli procesorskog vremena (Time Sharing).

Pri izvođenju procesa program izvodi instrukcijsku dretvu (thread), koja nije ista kao programski niz naredbi. Programske naredbe su one kako su zapisane u programu a instrukcijski thread su vremenski poredane naredbe, onako kako će dolaziti u procesor. Svaki proces ima jednu ili više dretvi koje se mogu izvoditi paralelno ili prividno paralelno.

Svi threadovi nekog procesa imaju zajednički memorijski prostor, za razliku od procesa gdje svaki proces ima svoj memorijski prostor.

 

Za rad s procesima koristit ćemo modul multithreading.

Funkcija cpu_count() vraća broj jezgri, odnosno procesa računala.

Primjer 1.

Provjerimo broj procesora (jezgri) računala.

Rješenje:

>>> from multiprocessing import *
>>> cpu_count()
8

Proces ćemo kreirati slično kao i je thread:

Process(target = ime_funkcije, args = (arg1, arg2,…))

Neke od metoda za rad s procesima su:

  • join([t]) – čeka da završi proces, ako je postavljen parametar t proces će se ugasiti nakon isteka vremena t. Ovo zapravo kaže glavnom programu da na tom mjestu čeka dok svi programi ne završe te tek nakon toga da ide na sljedeći korak,
  • start() – pokreće proces,
  • is_alive() – vraća istinu ako je proces aktivan, inače vraća false,
  • Terminate() gasi proces,
  • name vraća ime procesa,
  • pid vraća ID procesa.

Primjer 2.

Prikažimo sve procese koji se izvode na računalu te njihove ID-eve.

Rješenje:

Task manager.

upravitelj_zadataka

Funkcija curent_process() vraća objekt tipa Process, a koji predstavlja tekući proces.

Primjer 3:

Napišimo program koji će pokretati četiri procesa. Svaki proces prilikom svog pokretanja treba ispisati poruku: svoje ime i ID. Nadalje, proces treba zaustaviti na nekoliko (random) sekundi te ponovno ispisati ID procesa i vrijeme za koje je proces bio zaustavljen.

Rješenje (program multiprocessing1.py):

from multiprocessing import *
from time import *
from random import *

def f():
    p = current_process()
    print(f'ID: {p.pid} ime: {p.name}')
    t = randint(1, 10)
    sleep(t)
    print(f'Proces {p.pid} je završio nakon {t}s')
    return

if
__name__ == '__main__':
    procesi = [Process(target = f) for i in range(4)]
    for p in procesi:
        p.start()
    for p in procesi:
        p.join()

Program se mora pokrenutu u naredbenom retku (cmd) jer proces ne može pisati u IDLE ali može pisati u komandni prompt.

multiprocessing1 u cmd

Napomene uz zadatak:
Smisleno bi bilo u istoj petlji napraviti i start() i join() međutim to ne bi bilo dobro jer join() znači da program čeka s daljnjim operacijama sve dok se ne završi proces. To znači da bi nakon što se pokrene prvi proces i pozove join() program čekao da prvi proces završi te bi tek potom pokrenuo drugi proces i čekao da se on završi,….

Primjer 4:

Napišimo program koji će prebrojiti sve proste brojeve do 1000000 te će ispisati vrijeme  izvođenja programa.

Rješenje:

from time import *

def prost(N):
for i in range(2, round(N ** 0.5 + 1)):
if N % i == 0:
return False
return
True

def prosti(a, b):
t = 0
for i in range(a, b):
if prost(i):
t += 1
return t

if
__name__ == '__main__':
a = 2
b = 1000001
poc = time()
print(prosti(a, b))
kraj = time()
print(f'{(kraj - poc):.5f}')

Program se može pokrenuti i na uobičajeni način (Run - Run Module) . Nakon malog čekanja pojavit će se  ispis:

78498
12.32405
To znači da postoji 78 498 prostih brojeva manjih od 1 000 000 i vrijeme izvođenja ovog programa na nekom računalu trajalo je 12.32405 sekundi.

Primjer 5:

Izmijenimo prethodni primjer na način da dva procesa istovremeno broje proste brojeve te ispišimo vrijeme.

Napomene uz zadatak:

  • Razmislite kako bi se posao mogao ubrzati - ideja:  svakom procesu damo polovicu brojeva
  • Uočite da u ovom trenutku ne znamo:
    • koliko prostih brojeva je našao koji proces
    • je li posao dobro napravljen .
  • Problem je što se rezultat neće ispisivati u IDLE-u te program treba pokrenuti kroz cmd. Razlog tomu je slično kao i kod threadova – činjenica da proces ne može pristupiti IDLE-u.
  • Na početku programa dodati from multiprocessing import *

Rješenje:

from multiprocessing import *
from time import *

def prost(N):
for i in range(2, round(N**0.5 + 1)):
if N % i == 0:
return False
return
True

def prosti(a, b):
t = 0
for i in range(a, b):
if prost(i):
t += 1
return t

if
__name__ == '__main__':
a = 2
b = 1000001
poc = time()
p1 = Process(target = prosti, args = (a, b // 2))
p1.start()
p2 = Process(target = prosti, args = (b // 2 + 1, b))
p2.start()
p1.join()
p2.join()
kraj = time()
print(f'{(kraj - poc):.5f}')
Rezultat se neće ispisivati u IDLE-u te program treba pokrenuti kroz cmd.
cmd2
ili ako imate više otvorenih aplikacija sve se može odvijati malo sporije:
cmd3

Napomene uz zadatak:

  • Važno je uočiti da se ovo rješenje nije izvodilo duplo kraće od rješenja kada imamo samo jedan proces.
  • Razlozi:
    • prvi i drugi proces ne rade istu količinu posla (drugom proces ima daleko više posla jer radi s većim brojevima);
    • postupak pokretanja procesa zahtjeva neko vrijeme,…

Vrijeme je kraće za približno 30 % (visi o broju drugih pokrenutih aplikacija).

Zašto to vrijeme (7.79503) nije upola kraće od prvog dobivenog vremena (12.32405)?

Razlozi: 

  • Računalo neće uvijek istom brzinom izvoditi ove programe. Stoga ćemo gotovo za svako pokretanje programa dobivati različita vremena. Radi se o tome da osim ovih, naših procesa, računalo izvodi i još neke druge procese, a ovisno je o zahtjevnosti tih drugih procesa, računalo je dodijelilo vrijeme našim procesima.
  • Intervale za provjeru prostih brojeva nismo jednoliko podijelili. Naime, znatno je teže provjeriti je li primjerice broj 789 533 prost nego je li prost broj 11.
  • Računalu je potrebno neko vrijeme da kreira svaki od procesa...

Isprobajte ove programe i provjerite kako rade na vašem računalu i predajte ih na linku 
27. 5. - modul multithreading