Python Συνταγή 2 – Δουλεύοντας με Threads

Τα τελευταία χρόνια οι υπολογιστές γίνονται ολοένα και γρηγορότεροι και με ολοένα και μεγαλύτερη ισχύ. Οι αυξανόμενες ανάγκες όμως μας έχουν οδηγήσει σε κάποιους περιορισμούς οι οποίες έχουν να κάνουν με κανόνες της φυσικής πάνω στους οποίους βασίζονται οι υπολογιστές που χρησιμοποιούμε σήμερα και οι οποίοι θα ξεπεραστούν με τους κβαντικούς υπολογιστές στο μέλλον.

Για την ώρα όμως εαν έχουμε να κάνουμε μία δύσκολη δουλειά η οποία περιλαμβάνει πολλές διαδικασίες αυτό που συνήθως γίνεται είναι να σπάμε τη δουλειά σε παράλληλες διαδικασίες.

Η Python υποστηρίζει την εκτέλεση παράλληλων εργασιών και την παράλληλη εκτέλεση κώδικα είτε μέσω των threads είτε μέσω των processes.

Η εκτέλεση πολλών threads παράλληλα μπορεί να παρομοιαστεί με την εκτέλεση πολλών προγραμμάτων ταυτόχρονα όπως γίνεται στο λειτουργικό μας σύστημα με τα εξής χαρακτηριστικά/πλεονεκτήματα:

  • Μπορούν να επικοινωνούν μεταξύ τους εύκολα μιας και μοιράζονται τον ίδιο χώρο με το κεντρικό thread
  • Έχουν λίγες απαιτήσεις σε μνήμη
  • Η διαθέσιμη μνήμη μοιράζεται μεταξύ των threads
  • Υπάρχει ένα GIL (Global Interpreted Lock) για όλα τα threads

Ένα thread έχει αρχή , αριθμό εκτέλεσης και τέλος . Μπορούμε ανά πάσα ώρα και στιγμή να δούμε την κατάστασή του και να την τερματίσουμε ή να την θέσουμε σε παύση.

Στη συνταγή αυτή θα δούμε πώς μπορούμε να χρησιμοποιήσουμε τα threads για να λύνουμε τα προβλήματά μας γρηγορότερα.

import threading
import os
import math
import time

def calc():
    for i in range(0,9000000):
        math.sqrt(i)

timeStart = time.time()
thread_list = []

#θα δημιουργήσουμε τόσα threads οσα και τα cores του επεξεργαστή μας
for i in range(os.cpu_count()):
    print('Καταχώρηση Thread Νο: %d' %i)    
    thread_list.append(threading.Thread(target=calc))

#Εκκίνηση thread
for thread in thread_list:
    thread.start()

for thread in thread_list:
    thread.join()

timeEnd = time.time()
totalTime = timeEnd-timeStart
#Τυπώνουμε πόσα δευτερόλεπτα πήρε η εκτέλεση του προγράμματος
print("Συνολικός Χρόνος Εκτέλεσης σε δευτερόλεπτα : "+str(totalTime))

Η παραπάνω εφαρμογή υπολογίζει για 9000000 αριθμούς το τετράγωνό τους. Επίσης για να κάνουμε και ένα μικρό benchmark θα διαπιστώσουμε ενσωματώνουμε το module time της python όπου ξεκινάμε το χρονόμετρό μας στην αρχή και με την λήξη του προγράμματος απλά τυπώνουμε την διαφορά σε δευτερόλεπτα.

Όταν δουλεύουμε με threads η λογική που ακολουθούμε είναι η εξής :

  1. Δημιουργούμε το thread στο οποίο ορίζουμε τι θέλουμε να συμβεί. Η εντολή που γίνεται αυτό είναι η threading.Thread(target=calc) στην οποία λέμε ότι θέλουμε να τρέξει η συνάρτηση calc
  2. Αφού δημιουργήσουμε όλα τα thread πρέπει να τα ξεκινήσουμε με την συνάρτηση start()
  3. Για κάθε thread καλούμε την συνάρτηση join() η οποία εμποδίζει την εκτέλεση οποιουδήποτε άλλου κώδικα μέχρι το thread για το οποίο καλέσαμε την join ολοκληρωθεί .

Στον παρακάτω κώδικα εκτελούμε το παραπάνω πρόγραμμα χωρίς την χρήση thread .

import os
import math
import time

def calc():
    for i in range(0,9000000):
        math.sqrt(i)

timeStart = time.time()


#Εδω εκτελούμε την συνάρτηση calc σειριακά τόσες φορές όσα και τα cores του επεξεργαστή μας
for i in range(os.cpu_count()):
    print('Εκτέλεση συνάρτησης calc επανάληψη Νο. : %d' %i)    
    calc()


timeEnd = time.time()
totalTime = timeEnd-timeStart
#Τυπώνουμε πόσα δευτερόλεπτα πήρε η εκτέλεση του προγράμματος
print("Συνολικός Χρόνος Εκτέλεσης σε δευτερόλεπτα : "+str(totalTime))

Κάνοντας μία σύγκριση του χρόνου εκτέλεσης διαπιστώνουμε ότι σαφώς η χρήση threads επιταχύνει την διαδικασία σημαντικά. Συγκεκριμένα στον υπολογιστή μου έναν i5 7ης γενιάς με 16GB Ram ο χρόνος εκτέλεσης με χρήση threads ήταν :

Moras@DESKTOP-E1B009K MINGW64 /e/Dropbox/PROJECTS/PYTHON/python-recipes/threads (master)
$ python threads.py
Καταχώρηση Thread Νο: 0
Καταχώρηση Thread Νο: 1
Καταχώρηση Thread Νο: 2
Καταχώρηση Thread Νο: 3
Συνολικός Χρόνος Εκτέλεσης σε δευτερόλεπτα : 10.315426349639893

Ενώ χωρίς την χρήση threads :

Moras@DESKTOP-E1B009K MINGW64 /e/Dropbox/PROJECTS/PYTHON/python-recipes/threads (master)
$ python threads.py
Εκτέλεση συνάρτησης calc επανάληψη Νο. : 0
Εκτέλεση συνάρτησης calc επανάληψη Νο. : 1
Εκτέλεση συνάρτησης calc επανάληψη Νο. : 2
Εκτέλεση συνάρτησης calc επανάληψη Νο. : 3
Συνολικός Χρόνος Εκτέλεσης σε δευτερόλεπτα : 12.0001962184906

Δλδ δύο δευτερόλεπτα γρηγορότερη εκτέλεση του προγράμματος κάτι το οποίο είναι σημαντική διαφορά ειδικά εαν τον συγκεκριμένο κώδικα τον εκτελούμε αρκετές φορές.

Φυσικά ο κώδικας είναι διαθέσιμος στο github στην παρακάτω διεύθυνση :
https://github.com/delmoras/python-recipes/tree/master/threads

Σε επόμενη συνταγή θα μιλήσουμε για χρήση των Processes.