Sunteți pe pagina 1din 12

2015/04/05 02:50

1/12

Fire de execuie n Python

Fire de execuie n Python


Obiective
Scopul acestui laborator l reprezint familiarizarea cu lucrul cu thread-uri i obiecte de sincronizare n
Python. Pentru acestea avei nevoie de cunoaterea elementelor de sintax Python prezentate n
laboratorul 1 i de lucrul cu clase prezentat n laboratorul acesta.

Recapitulare laborator 1
n cadrul laboratorului de ASC folosim versiunea 2.x a Python-ului (cel putin 2.6). Aceast versiune
este incompatibil cu Python 3.x i unele construcii sau biblioteci este posibil s nu fie suportate nici
de versiunile anterioare 2.6.
Particulariti de limbaj:

Indentarea este obligatorie pentru a delimita blocurile de cod.


Este dynamically i strongly typed:
dynamically typed - pentru c tipurile variabilelor nu sunt precizate explicit n cod, i acestea se
poate schimba pe msur ce atribuim valori variabilelor
strongly typed - pentru c nu se pot face conversii de tip implicite (e.g. adunare de string cu
int)
pentru conversiile explicite ntre tipurile numerice, boolean i iruri de caractere folosii
funciile built-in
Keywords:
None este echivalentul null n Java
pass este echivalentul unui bloc {} din c/java
Tipurile de date cele mai folosite sunt int, float, string, boolean, list, tuple, dict.

Un fiier de cod Python este considerat un modul. Pentru a folosi alte module utilizm import n
urmtoarele modaliti:
import_example.py
import random
random.randint(0,4)

# trebuie specificat numele modulului

import random as rand


rand.randint(0,4)

# folosire alias pentru numele modulului


# trebuie specificat alias-ul

from random import *


randint(0,4)

# import tot continutul modulului


# nu mai trebuie specificat numele

ASC Wiki - https://cs.curs.pub.ro/wiki/asc/

Last update: 2015/03/14 17:24

asc:lab2:index

https://cs.curs.pub.ro/wiki/asc/asc:lab2:index

modulului
from random import randint
randint(0,4)

# import doar randint

Construcia if __name__ == "__main__" delimiteaz 'main'-ul unui modul. Aceasta nu este


obligatorie, ns dac nu e folosit, orice cod cu indentare top level s-ar executa de fiecare dat cnd
fiierul este parsat (ex: cnd este importat).
Funciile se declar folosind keyword-ul def i nu li se specific tip de return sau tipuri pentru
parametri. Se poate simula suprancrcarea (overloading) metodelor folosind parametri cu valori
implicite.
func_example.py
def f(a, b="world", c=0):
print " ".join([a, b, str(c)])
f("hello")
f("hello",
f("hello",
f("hello",
f("hello",

"lab")
"lab", 2)
c=2)
"lab", c=2)

#
#
#
#
#

hello
hello
hello
hello
hello

world 0
lab 0
lab 2
world 2
lab 2

Ce este un thread?
Sistemele de calcul moderne sunt capabile de a executa mai multe operaii n acelasi timp. Sistemul
de operare este cel care permite rularea mai multor aplicaii simultan, dar aceast idee se poate
extinde i la nivelul unei aplicaii. De exemplu, o aplicaie ce ruleaz un stream video online trebuie
simultan s citeasc coninutul video de pe reea, s l decomprime, s actualizeze display-ul local cu
aceste informaii etc. Spunem c aplicaiile ce ofer aceste capabiliti constituie un software
concurent.
Deci ce este concurena? Concurena este proprietatea unei logici de program de a putea executa
simultan un set de task-uri. Paralelismul reprezint o metod de implementare a acestei
paradigme de programare ce permite rularea unui set de task-uri ntr-un mod care utilizeaz core-uri
multiple, procesoare multiple sau chiar mai multe maini (ntr-o structur de tip cluster de exemplu).
Thread-urile reprezint o metod de implementare a concurenei, fiind fire de execuie create (
spawned) n cadrul unui program principal (process) ce execut concurent task-uri definite de
programator. Un fir de execuie este parte a unui proces, iar implementarea difer de la un sistem de
operare la altul. Mai multe thread-uri pot exista n cadrul aceluiai proces, ele partajnd anumite
resurse: memorie, descriptori I/O etc. n aceast privin thread-urile difer de procese prin faptul c
variabilele globale pot fi accesate de ctre toate thread-urile unui proces i pot servi ca mediu de
comunicaie ntre thread-uri. Fiecare thread are totui i un set propriu de variabile locale. Din acest
motiv thread-urile mai sunt numite i lightweight processes.
https://cs.curs.pub.ro/wiki/asc/

Printed on 2015/04/05 02:50

2015/04/05 02:50

3/12

Fire de execuie n Python

n cadrul unui sistem uni-procesor, rularea concurent a mai multor fire de execuie se face prin
metoda partajrii timpului de execuie (time sharing / time division / time slicing), sistemul de operare
alternnd succesiv ntre execuia thread-urile active (percepia este cea a rulrii simultane ns n
realitate un singur thread ruleaz la un moment dat).
n cadrul unui sistem multi-procesor sau multi-core, thread-urile vor rula n general cu adevrat
simultan, cu fiecare procesor rulnd un thread specific.
Din punct de vedere al suportului pentru programarea multithreading limbajele se mpart n dou
categorii:

limbaje cu thread-uri utilizator (green threads) ce nu sunt vizibile sistemului de operare, ci doar la
nivelul unui singur proces (gsii vreun dezavantaj?)
limbaje cu thread-uri native (adesea denumite i kernel threads) ce sunt vizibile la nivelul sistemului
de operare, ceea ce permite execuia lor paralel pe mai multe core-uri

Clase i obiecte n Python


Trebuie subliniat c n Python, cuvntul obiect nu se refer neaprat la instana unei clase. Clasele
n sine sunt obiecte, iar, n sens mai larg, n Python toate tipurile de date sunt obiecte. Exist tipuri de
date care nu sunt clase: numerele ntregi, listele, fiierele.
O clas, n sensul C++/Java, se creaz n Python prin folosirea cuvantului cheie class. Exemplul de
mai jos creeaz un obiect de tip class cu numele ClassName. Acesta este echivalent cu o clas
C++/Java numit ClassName. Interiorul clasei poate conine definiii de metode sau clase i atribuiri
de variabile. Clasa este derivat din SuperClass1 i din SuperClass2. Spre deosebire de Java, numele
fiierului surs nu trebuie s fie la fel cu al vreunei clase definite n el.
class ClassName (SuperClass1, SuperClass2):
[interiorul clasei]
Clasele suport multiple inheritence i nu exist un contract propriu-zis pentru interfee. Pentru a
crea clase abstracte exist modulul abc (Abstract Base Classes). Pentru metodele pe care vrei s le
considerai abstracte putei transmite excepia NotImplementedError sau putei aduga n corpul
funciei doar keyword-ul pass.
n lucrul cu clase, trebuie avute n vedere urmtoarele reguli:

Primul argument pentru metodele unei clase este ntotdeauna obiectul surs, numit self,
echivalent-ul lui this.
Cnd ne referim la membrii clasei, trebuie s folosim self.membru, ntr-un mod asemanator cu
folosirea this din Java (doar c n Python este obligatoriu s folosim self nu doar pentru a face
distincie ntre cmpurile clasei i parametrii/variabilele cu aceleai nume din funcii).
Metoda special __init__() este apelat la instanierea clasei i poate fi considerat un
constructor. Definirea metodelor __init__() este opional.
Metoda special __del__() este apelat cnd nu mai sunt referine la acest obiect i poate fi
asemuit cu un destructor. Definirea metodelor __del__() este opional.
n cazul motenirii, n metoda __init__() trebuie nti apelat __init__()-ul claselor printe.
Implicit toate cmpurile si metodele claselor sunt publice. Pentru a declara un cmp/metod privat

ASC Wiki - https://cs.curs.pub.ro/wiki/asc/

Last update: 2015/03/14 17:24

asc:lab2:index

https://cs.curs.pub.ro/wiki/asc/asc:lab2:index

numele acesteia trebuie prefixat cu __ (mai multe detalii putei afla aici).
Instanierea se face prin apelarea obiectului clas, posibil cu argumente.

class_example.py
class Student:
""" O clasa care reprezinta un student. Comentariile docstring se
pun dupa declaratie :) """
def __init__(self, name, grade=5):
# constructor cu parametru
default; echivalent cu mai multi constructori overloaded
self.name = name
# campurile clasei pot fi
declarate oriunde!
self.change_grade(grade)
# apelul unei metode a clasei
def change_grade(self, grade):
intotdeauna 'self'
self.grade = grade

# primul parametru este


# adauga nou camp clasei

x = Student("Alice")
y = Student("Bob", 10)
x.change_grade(8)

Clasele Python pot avea membri statici. n cazul cmpurilor, ele sunt declarate n afara oricrei
metode a clasei. Pentru metode avem dou variante: una folosind decoratorul @staticmethod,
cealalt folosind funcia built-in staticmethod. Observai c metodele statice nu au parametrul self.
static_example.py
class Util:
x = 2
@staticmethod
def do_stuff():
print "stuff"

# camp static
# metoda statica

def do_otherstuff():
# alta varianta de a declara o metoda
statica
print "other stuff"
do_otherstuff = staticmethod(do_otherstuff)
print Util.x
Util.do_stuff()
Util.do_otherstuff()

Clase Python pe scurt:

https://cs.curs.pub.ro/wiki/asc/

Printed on 2015/04/05 02:50

2015/04/05 02:50

5/12

Fire de execuie n Python

trebuie s folosii self.nume_funcie sau self.nume_variabila


o clas poate moteni mai multe clase
__init__() este numele constructorului. Putei avea un singur constructor, pentru a simula mai
muli contructori folosii parametri default.
putei avea metode i variabile statice
nu avei access modifiers
instaniere: nume_instanta = NumeClasa(argumente_constructor)

Programare concurent n Python


n Python, programarea concurent este facilitat de modulul threading. Acest modul ofer clasa
Thread, care permite crearea i managementul thread-urilor, precum i o serie de clase (Condition,
Event, Lock, RLock, Semaphore) care ofer modaliti de sincronizare i comunicare ntre
thread-urile unui program Python.

Thread-uri
Un fir de execuie concurent este reprezentat n Pyhton de clasa Thread. Cel mai simplu mod de a
specifica instruciunile care se doresc a fi rulate concurent, este de a apela constructorul lui Thread cu
numele unei funcii care conine aceste instruciuni, precum n exemplul urmtor. Pornirea thread-ului
se face apoi cu metoda start(), iar pentru a atepta terminarea execuiei thread-ului se folosete
metoda join().
exemplul1.py
from threading import Thread
def my_concurrent_code(nr, msg):
""" Functie care va fi rulata concurent """
print "Thread", nr, "says:", msg
# creeaza obiectele corespunzatoare thread-urilor
t1 = Thread(target = my_concurrent_code, args = (1, "hello from thread"))
t2 = Thread(target = my_concurrent_code, args = (2, "hello from other
thread"))
# porneste thread-urile
t1.start()
t2.start()
# executia thread-ului principal continua de asemenea
print "Main thread says: hello from main"
# asteapta terminarea thread-urilor
t1.join()
ASC Wiki - https://cs.curs.pub.ro/wiki/asc/

Last update: 2015/03/14 17:24

asc:lab2:index

https://cs.curs.pub.ro/wiki/asc/asc:lab2:index

t2.join()

Se folosete parametrul target al constructorului pentru a pasa numele funciei concurente i,


opional, pot fi folosii parametrii args sau kwargs pentru a specifica argumentele funciei concurente,
dac ele exist. args este folosit pentru a trimite argumentele funciei concurente ca un tuplu, iar
kwargs este folosit pentru a trimite argumentele ca un dicionar.

Pentru a diferenia un tuplu cu un singur element de folosirea obinuit a parantezelor se utilizeaz


urmtoarea sintax:
#
t
#
t

t
=
t
=

contine int-ul 42
(42)
contine un tuplu cu un singur element
(42,)

Crearea unui obiect Thread nu pornete execuia thread-ului. Acestu lucru se ntmpl doar dup
apelul metodei start().
O metod alternativ de a specifica instruciunile care se doresc a fi rulate concurent este de a crea o
subclas a lui Thread care suprascrie metoda run(). Se poate de asemenea suprascrie i metoda
__init__() (constructorul) pentru a primi argumentele cu care vor fi iniializate cmpurile proprii
subclasei. Dac optai pentru aceast abordare nu este indicat s suprascriei alte metode ale clasei
Thread, dect constructorul i run().
exemplul2.py
from threading import Thread
class MyThread(Thread):
""" Clasa care incapsuleaza codul nostru concurent """
def __init__(self, nr, msg):
Thread.__init__(self)
self.nr = nr
self.msg = msg
def run(self):
print "Thread", self.nr, "says:", self.msg
# creeaza obiectele corespunzatoare thread-urilor
t1 = MyThread(1, "hello from thread")
t2 = MyThread(2, "hello from other thread")
# porneste thread-urile
t1.start()
t2.start()
https://cs.curs.pub.ro/wiki/asc/

Printed on 2015/04/05 02:50

2015/04/05 02:50

7/12

Fire de execuie n Python

# executia thread-ului principal continua de asemenea


print "Main thread says: hello from main"
# asteapta terminarea thread-urilor
t1.join()
t2.join()

La suprascrierea constructorului clasei Thread nu uitai s apelai i constructorul clasei de baz.


Pe lng clasa Thread i clasele de sincronizare, modulul threading mai conine i o serie de funcii
utile n debugging-ul programelor cu mai multe fire de execuie:

Funcia active_count() returneaz numrul curent de thread-uri active (care ruleaz).


Funcia current_thread() returnez obiectul Thread corespunztor firului de execuie care a rulat
apelul funciei. Acest obiect poate fi folosit pentru a afia informaii despre thread-ul curent, cum ar
fi numele acestuia. Numele unui thread este implicit Thread-N (unde N este un numr unic), dar
poate fi uor schimbat prin folosirea parametrului name al constructorului clasei Thread.
Funcia enumerate() returneaz o list cu toate obiectele Thread active.

Interpretorul cel mai popular de Python (CPython) folosete un lock intern (GIL - Global Interpreter
Lock) pentru a simplifica implementarea unor operaii de nivel sczut (managementul memoriei,
apelul extensiilor scrise n C etc.). Acest lock permite execuia unui singur thread n interpretor la un
moment dat i limiteaz paralelismul i performana thread-urilor Python. Mai multe detalii despre GIL
putei gsi n aceast prezentare.

Elemente de sincronizare
Pentru ca un program concurent s funcioneze corect este nevoie ca firele sale de execuie s
coopereze n momentul n care vor s acceseze date partajate. Aceast cooperare se face prin
intermediul partajrii unor elemente de sincronizare care pun la dispoziie un API ce ofer anumite
garanii despre starea de execuie a thread-urilor care le folosesc.

Thread
Pe lng facilitile de creare a noi fire de execuie, obiectele de tip Thread reprezint i cele mai
simple elemente de sincronizare, prin intermediul metodelor start() i join().
Metoda start() garanteaz c toate rezultatele thread-ului care o apeleaz (s-l numim t1), pn n
punctul apelului, sunt disponibile i n thread-ul care va porni (s-l numim t2). A se observa c nu se
ofer nici un fel de garanie despre rezultatele lui t1 care urmeaz dup apel. t2 nu poate face nici o
presupunere n acest caz, fr a folosi alte elemente de sincronizare.

ASC Wiki - https://cs.curs.pub.ro/wiki/asc/

Last update: 2015/03/14 17:24

asc:lab2:index

https://cs.curs.pub.ro/wiki/asc/asc:lab2:index

Metoda join() garanteaz thread-ului care o apeleaz (s-l numim t1) c thread-ul asupra creia este
apelat (s-l numim t2) s-a terminat i nu mai acceseaz date partajate. n plus toate rezultatele lui t2
sunt disponibile i pot fi folosite de ctre t1. A se observa c, fa de metoda start(), metoda join()
blocheaz execuia thread-ului care o apeleaz (t1) pn cnd t2 i termin execuia. Spunem c
join() este o metod blocant.

Lock
Lock-ul este un element de sincronizare care ofer acces exclusiv la poriunile de cod protejate de
ctre lock (cu alte cuvinte definete o seciune critic). Python pune la dispoziie clasa Lock pentru a
lucra cu acest element de sincronizare. Un obiect de tip Lock se poate afla ntr-una din urmtoarele
dou stri: blocat sau neblocat, implicit, un obiect de tip Lock fiind creat n starea neblocat. Sunt
oferite dou operaii care controleaz starea unui lock: acquire() i release().
Metoda acquire() va trece lock-ul n starea blocat. Dac lock-ul se afla deja n starea blocat, thread-ul
care a apelat acquire() se va bloca pn cnd lock-ul este eliberat (pentru a putea fi blocat din nou).
Metoda release() este cea care trece lock-ul n starea deblocat. Cele dou metode garanteaz c un
singur thread poate deine lock-ul la un moment dat, oferind astfel posibilitatea ca un singur thread
s execute seciunea de cod critic. O alt garanie a lock-ului este c toate rezultatele thread-ului
care a efectuat release() sunt disponibile i pot fi folosite de urmtoarele thread-uri care execut
acquire().

Spre deosebire de un mutex (ex: pthread_mutex), n Python, metodele acquire() i release() pot fi
apelate de thread-uri diferite. Cu alte cuvinte un thread poate face acquire() i alt thread poate face
release(). Datorit acestei diferene subtile nu este recomandat s folosii un obiect Lock n acest mod.
Pentru a reduce confuziile i a obine acelai efect se poate folosi un obiect BoundedSemaphore
iniializat cu valoarea 1.
Lock-ul este utilizat n majoritatea cazurilor pentru a proteja accesul la structuri de date partajate,
care altfel ar putea fi modificate de un fir de execuie n timp ce alte fire de execuie ncearc
simultan s citeasc sau s modifice i ele aceeai structur de date. Pentru a rezolva aceast situaie,
poriunile de cod care acceseaz structura de date partajat sunt ncadrate ntre apeluri acquire() i
release() pe acelai obiect Lock partajat de toate thread-urile care vor sa acceseze structura.
Exemplul de mai jos prezint folosirea unui lock pentru a proteja accesul la o list partajat de mai
multe thread-uri.
exemplul3.py
from threading import Lock, Thread
def inc(lista, lock, index, n):
""" Incrementeaza elementul index din lista de n ori """
for i in xrange(n):
lock.acquire()
lista[index] += 1
lock.release()
https://cs.curs.pub.ro/wiki/asc/

Printed on 2015/04/05 02:50

2015/04/05 02:50

9/12

Fire de execuie n Python

def dec(lista, lock, index, n):


""" Decrementeaza elementul index din lista de n ori """
for i in xrange(n):
lock.acquire()
lista[index] -= 1
lock.release()
# lista si lock-ul care o protejeaza
my_list = [0]
my_lock = Lock()
# thread-urile care modifica elemente din lista
t1 = Thread(target = inc, args = (my_list, my_lock, 0, 100000))
t2 = Thread(target = dec, args = (my_list, my_lock, 0, 100000))
# lista inainte de modificari
print my_list
t1.start()
t2.start()
t1.join()
t2.join()
# lista dupa modificari
print my_list

Putei folosi construcia with pentru a delimita o seciune critic astfel:


def inc(lista, lock, index, n):
for i in xrange(n):
with lock:
lista[index] += 1

Semaphore
Semaforul este un element de sincronizare cu o interfa asemntoare Lock-ului (metodele acquire()
i release()) ns cu o comportare diferit. Python ofer suport pentru semafoare prin intermediul
clasei Semaphore.
Un Semaphore menine un contor intern care este decrementat de un apel acquire() i incrementat
de un apel release(). Metoda acquire() nu va permite decrementarea contorului sub valoarea 0, ea
blocnd execuia thread-ului n acest caz pn cnd contorul este incrementat de un release().
ASC Wiki - https://cs.curs.pub.ro/wiki/asc/

Last update: 2015/03/14 17:24

asc:lab2:index

https://cs.curs.pub.ro/wiki/asc/asc:lab2:index

Metodele acquire() i release() pot fi apelate fr probleme de thread-uri diferite, aceast utilizare
fiind des ntlnit n cazul semafoarelor.
Un exemplu clasic de folosire a semaforului este acela de a limita numrul de thread-uri care
acceseaz concurent o resurs precum n exemplul urmtor:
exemplul4.py
from random import randint, seed
from threading import Semaphore, Thread
from time import sleep
def access(nr, sem):
sem.acquire()
print "Thread-ul", nr, " acceseaza"
sleep(randint(1, 4))
print "Thread-ul", nr, " a terminat"
sem.release()
# initializam semaforul cu 3 pentru a avea maxim 3 thread-uri active la
un moment dat
semafor = Semaphore(value = 3)
# stocam obiectele Thread pentru a putea face join
thread_list = []
seed()
# pornim thread-urile
for i in xrange(10):
thread = Thread(target = access, args = (i, semafor))
thread.start()
thread_list.append(thread)
# asteptam terminarea thread-urilor
for i in xrange(len(thread_list)):
thread_list[i].join()

Exerciii
1. Hello Thread - rezolvai exerciiile din fiierul task1.py din scheletul de laborator. (4p)
2. Protejarea variabilelor folosind locks - rezolvai exerciiile din fiierul task2.py din scheletul de
laborator. (3p)
3. Implementai problema producator-consumator folosind semafoare. (3p)
Mai muli productori i mai multi consumatori comunic printr-un buffer partajat, limitat la un
numr fix de valori. Un productor pune cte o valoare n buffer iar un consumator poate s ia
cte o valoare din buffer.

https://cs.curs.pub.ro/wiki/asc/

Printed on 2015/04/05 02:50

2015/04/05 02:50

11/12

Fire de execuie n Python

Avei nevoie de dou semafoare, unul pentru a indica dac se mai pot pune valori n buffer i
cellalt pentru a arta dac exist vreo valoare care poate fi luat din buffer de ctre
consumatori.
4. Implementai problema filosofilor. (2p)
Se consider mai muli filozofi ce stau n jurul unei mese rotunde. n mijlocul mesei este o farfurie
cu spaghete. Pentru a putea mnca, un filozof are nevoie de dou beisoare. Pe mas exist cte
un beior ntre fiecare doi filozofi vecini. Regula este c fiecare filozof poate folosi doar
beioarele din imediata sa apropriere. Trebuie evitat situaia n care nici un filozof nu poate
acapara ambele beioare. Comportamentul tuturor filozofilor trebuie s fie identic.

Codul protejat (ncadrat) de lockuri nu e bine s conin print-uri sau instruciuni ce nu lucreaz cu
variabila partajat (e.g. sleep).
Nu este recomandat ca ntr-o aplicaie concurent s avei print-uri, i nici lock-uri pe print-uri, acest
lucru afectnd comportarea threadurilor. n laborator se folosesc print-uri mai mult in scop de
debugging, i v recomandm s folosii format sau concatenare (+) pentru a obine afiri atomice.

Resurse

Responsabilii acestui laborator: Adriana Drghici, Dan Dragomir


PDF laborator
Schelet laborator
Soluie laborator

Referine
Documentaie module

modulul thread
modulul threading - Thread, Lock, Semaphore

Detalii legate de implementare

What kinds of global value mutation are thread-safe


Implementarea obiectelor de sincronizare n CPython
Python Threads and the Global Interpreter Lock
Understanding the Python GIL (prezentare foarte bun i amuzant)
PyPy transactional memory instead of GIL

Despre concuren i obiecte de sincronizare

Introduction to semaphores (video)


Understanding Threading in Python
Little book of semaphores
Programming on Parallel Machines (Chapter 3)

ASC Wiki - https://cs.curs.pub.ro/wiki/asc/

Last update: 2015/03/14 17:24

asc:lab2:index

https://cs.curs.pub.ro/wiki/asc/asc:lab2:index

From:
https://cs.curs.pub.ro/wiki/asc/ - ASC Wiki
Permanent link:
https://cs.curs.pub.ro/wiki/asc/asc:lab2:index
Last update: 2015/03/14 17:24

https://cs.curs.pub.ro/wiki/asc/

Printed on 2015/04/05 02:50