Višedretvenost u Pythonu

U primjerima koji slijede koristi se modul threading za upravljanje dretvama. Funkcija za stvaranje dretve je Thread. Npr.

opisnik = threading.Thread ( target = početna_funkcija, args = (arg1,arg2) )
stvoriti će dretvu čija je početna funkcija početna_funkcija a parametri varijable arg1 i arg2.
Parametar args je tip podataka n-torka (tuple). Kada bi bio samo jedan argument, onda ga treba navesti sa zarezom args = (arg1,).

Stvorena dretva neće automatski krenuti s radom. Potrebno je pozvati metodu start nad opisnikom stvorene dretve:

opisnik.start()

Glavna dretva može čekati na završetak stvorene dretve metodom join:

opisnik.join()

Primjer dretve-0.py

U primjeru se stvara 5 dretvi koje svaka po 10000000 puta povećavaju zajedničku varijablu a.

''' Osnovni primjer s dretvama. '''
import time, threading

BR_DRETVI = 5
BR_ITERACIJA = 10000000
a = 1

def posao_dretve (id):
	''' Početna funkcija za nove dretve '''
	global a
	print ( "Kreće dretva " + str(id) )
	for i in range(BR_ITERACIJA):
		a = a + 1

def main():
	dretva = [None] * BR_DRETVI # polje za opisnike dretvi

	# stvaranje i pokretanje dretvi
	for i in range(BR_DRETVI):
		dretva[i] = threading.Thread ( target = posao_dretve, args = (i+1,) )
		dretva[i].start()

	# čekanje na kraj rada svih dretvi
	for i in range(BR_DRETVI):
		dretva[i].join ()

	print ( "Konačna vrijednost za a = " + str(a) +
		" (idealno " + str(BR_DRETVI*BR_ITERACIJA)+")" )

if __name__ == "__main__":
	main()

# 2 x Ctrl+C prekida, ili Ctrl+\
Obzirom da se varijabla a može mijenjati i paralelno, konačna vrijednost može biti manja od očekivane. To se može spriječiti sinkronizacijskim mehanizmima.

Drugi problem navedena primjer je prihvat signala. Signale u Pyhtonu prihvaća i obrađuje samo glavna dretva. Međutim, ako je ona blokirana, kao u primjeru na pozivu join() onda nastaju problemi (potrebno je 2 puta poslati signal sa Ctrl+C).
Bolje rješenje je definirati funkciju za prihvat ta signala, a u glavnoj dretvi pozivu join dodati argument timeout kojim će blokiranje biti vremenski ograničeno, ali se može pozivati u petlji. Npr. prema primjeru:

while opisnik.is_alive():
   opisnik.join ( timeout = 1 )
Time će glavna dretva čekati na završetak druge, ali ne beskonačno. U gornjem primjeru najviše sekundu. Ukoliko je signal došao u međuvremenu on će se obraditi nakon isteka jedne sekunde. Funkcija is_alive() će vratiti istinu (True) ukoliko dretva još uvije radi (nije u međuvremnu završila s radom).

Primjer dretve-1.py

Idući primjer prikazuje moguće upravljanje dretvama i u slučaju potrebe prihvata signala.
import time, threading, signal, sys

kraj = False     # na signal ova se varijabla mijenja

BR_DRETVI = 3    # pretpostavljen broj dretvi
BR_ITERACIJA = 5 # pretpostavljen broj iteracija

def signal_kraj ( sig_num, frame ):
	''' Na signal SIGINT (Ctrl+C) program završava '''
	print ( "\nPrimljen signal za završetak ... ")
	global kraj
	kraj = True

def posao_dretve (id):
	''' Početna funkcija za nove dretve '''
	i = 1
	while not kraj: # dok signal za kraj nije došao
		print ( "Dretva " + str(id) + " iteracija " + str(i) )
		time.sleep(1.0)
		i = i + 1
		if i > BR_ITERACIJA:
			break

def main():
	signal.signal ( signal.SIGINT, signal_kraj )

	global BR_DRETVI, BR_ITERACIJA
	if len(sys.argv) == 3:
		BR_DRETVI = int(sys.argv[1])
		BR_ITERACIJA = int(sys.argv[2])

	dretva = [None] * BR_DRETVI
	for i in range(BR_DRETVI):
		dretva[i] = threading.Thread ( target = posao_dretve, args = (i+1,) )
		dretva[i].start()

	for i in range(BR_DRETVI):
		while dretva[i].is_alive():
			dretva[i].join (timeout=1)

	if not kraj:
		print ( "Normalan završetak programa" )

if __name__ == "__main__":
	main()