Multithreading in Python με Παράδειγμα: Μάθετε το GIL σε Python

Η γλώσσα προγραμματισμού python σάς επιτρέπει να χρησιμοποιείτε πολυεπεξεργασία ή πολυνηματική. Σε αυτό το σεμινάριο, θα μάθετε πώς να γράφετε εφαρμογές πολλαπλών νημάτων Python.

Τι είναι ένα νήμα;

Ένα νήμα είναι μια μονάδα εκτέλεσης σε ταυτόχρονο προγραμματισμό. Το Multithreading είναι μια τεχνική που επιτρέπει σε μια CPU να εκτελεί πολλές εργασίες μιας διεργασίας ταυτόχρονα. Αυτά τα νήματα μπορούν να εκτελεστούν μεμονωμένα ενώ μοιράζονται τους πόρους διεργασίας τους.

Τι είναι μια Διαδικασία;

Μια διαδικασία είναι βασικά το πρόγραμμα σε εκτέλεση. Όταν ξεκινάτε μια εφαρμογή στον υπολογιστή σας (όπως ένα πρόγραμμα περιήγησης ή πρόγραμμα επεξεργασίας κειμένου), το λειτουργικό σύστημα δημιουργεί ένα διαδικασία.

Τι είναι το Multithreading μέσα Python?

Multithreading in Python Ο προγραμματισμός είναι μια πολύ γνωστή τεχνική κατά την οποία πολλαπλά νήματα σε μια διεργασία μοιράζονται τον χώρο δεδομένων τους με το κύριο νήμα, γεγονός που καθιστά την ανταλλαγή πληροφοριών και την επικοινωνία εντός των νημάτων εύκολη και αποτελεσματική. Τα νήματα είναι ελαφρύτερα από τις διαδικασίες. Τα πολλαπλά νήματα μπορούν να εκτελεστούν μεμονωμένα ενώ μοιράζονται τους πόρους διεργασίας τους. Ο σκοπός του multithreading είναι η εκτέλεση πολλαπλών εργασιών και λειτουργιών κελιών ταυτόχρονα.

Τι είναι η Πολυεπεξεργασία;

Πολυεπεξεργασία σας επιτρέπει να εκτελείτε πολλές άσχετες διεργασίες ταυτόχρονα. Αυτές οι διεργασίες δεν μοιράζονται τους πόρους τους και επικοινωνούν μέσω IPC.

Python Multithreading έναντι Multiprocessing

Για να κατανοήσετε τις διαδικασίες και τα νήματα, εξετάστε το εξής σενάριο: Ένα αρχείο .exe στον υπολογιστή σας είναι ένα πρόγραμμα. Όταν το ανοίγετε, το λειτουργικό σύστημα το φορτώνει στη μνήμη και η CPU την εκτελεί. Η παρουσία του προγράμματος που εκτελείται τώρα ονομάζεται διεργασία.

Κάθε διαδικασία θα έχει 2 βασικά στοιχεία:

  • Ο Κώδικας
  • Τα δεδομένα

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

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

Γιατί να χρησιμοποιήσετε το Multithreading;

Το Multithreading σάς επιτρέπει να αναλύσετε μια εφαρμογή σε πολλαπλές υπο-εργασίες και να εκτελέσετε αυτές τις εργασίες ταυτόχρονα. Εάν χρησιμοποιείτε σωστά το multithreading, η ταχύτητα, η απόδοση και η απόδοση της εφαρμογής σας μπορούν να βελτιωθούν.

Python Multi Threading

Python υποστηρίζει κατασκευές τόσο για πολυεπεξεργασία όσο και για πολυνηματική. Σε αυτό το σεμινάριο, θα εστιάσετε κυρίως στην εφαρμογή πολυήμα εφαρμογές με python. Υπάρχουν δύο κύριες μονάδες που μπορούν να χρησιμοποιηθούν για το χειρισμό νημάτων μέσα Python:

  1. The νήμα ενότητα, και
  2. The σπείρωμα ενότητα

Ωστόσο, στην python, υπάρχει επίσης κάτι που ονομάζεται παγκόσμιο κλείδωμα διερμηνέα (GIL). Δεν επιτρέπει μεγάλο κέρδος απόδοσης και μπορεί ακόμη και μείωση την απόδοση ορισμένων εφαρμογών πολλαπλών νημάτων. Θα μάθετε τα πάντα για αυτό στις επόμενες ενότητες αυτού του σεμιναρίου.

Οι ενότητες Thread και Threading

Οι δύο ενότητες για τις οποίες θα μάθετε σε αυτό το σεμινάριο είναι οι μονάδα νήματος και την μονάδα σπειρώματος.

Ωστόσο, η μονάδα νήματος έχει από καιρό καταργηθεί. Ξεκινώντας με Python 3, έχει χαρακτηριστεί ως απαρχαιωμένο και είναι προσβάσιμο μόνο ως __Νήμα για συμβατότητα προς τα πίσω.

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

Η ενότητα του νήματος

Η σύνταξη για τη δημιουργία ενός νέου νήματος χρησιμοποιώντας αυτήν την ενότητα είναι η εξής:

thread.start_new_thread(function_name, arguments)

Εντάξει, τώρα έχετε καλύψει τη βασική θεωρία για να ξεκινήσετε την κωδικοποίηση. Άνοιξε λοιπόν IDLE ή ένα σημειωματάριο και πληκτρολογήστε τα εξής:

import time
import _thread

def thread_test(name, wait):
   i = 0
   while i <= 3:
      time.sleep(wait)
      print("Running %s\n" %name)
      i = i + 1

   print("%s has finished execution" %name)

if __name__ == "__main__":
    
    _thread.start_new_thread(thread_test, ("First Thread", 1))
    _thread.start_new_thread(thread_test, ("Second Thread", 2))
    _thread.start_new_thread(thread_test, ("Third Thread", 3))

Αποθηκεύστε το αρχείο και πατήστε F5 για να εκτελέσετε το πρόγραμμα. Εάν όλα έγιναν σωστά, αυτό είναι το αποτέλεσμα που θα πρέπει να δείτε:

Η ενότητα του νήματος

Θα μάθετε περισσότερα για τις συνθήκες αγώνα και πώς να τις χειριστείτε στις επόμενες ενότητες

Η ενότητα του νήματος

ΚΩΔΙΚΟΣ ΕΞΗΓΗΣΗ

  1. Αυτές οι δηλώσεις εισάγουν την ενότητα χρόνου και νήματος που χρησιμοποιούνται για τον χειρισμό της εκτέλεσης και της καθυστέρησης του Python νήματα.
  2. Εδώ, έχετε ορίσει μια συνάρτηση που ονομάζεται thread_test, που θα κληθεί από το start_new_thread μέθοδος. Η συνάρτηση εκτελεί έναν βρόχο while για τέσσερις επαναλήψεις και εκτυπώνει το όνομα του νήματος που την κάλεσε. Μόλις ολοκληρωθεί η επανάληψη, εκτυπώνει ένα μήνυμα που λέει ότι το νήμα έχει ολοκληρωθεί η εκτέλεση.
  3. Αυτή είναι η κύρια ενότητα του προγράμματός σας. Εδώ, απλά καλείτε το start_new_thread μέθοδος με το thread_test λειτουργεί ως όρισμα. Αυτό θα δημιουργήσει ένα νέο νήμα για τη συνάρτηση που μεταβιβάζετε ως όρισμα και θα ξεκινήσει να την εκτελείτε. Σημειώστε ότι μπορείτε να αντικαταστήσετε αυτό το (νήμα_test) με οποιαδήποτε άλλη συνάρτηση που θέλετε να εκτελέσετε ως νήμα.

Το Threading Module

Αυτή η ενότητα είναι η υψηλού επιπέδου υλοποίηση του threading σε python και το de facto πρότυπο για τη διαχείριση εφαρμογών πολλαπλών νημάτων. Παρέχει ένα ευρύ φάσμα χαρακτηριστικών σε σύγκριση με τη μονάδα νήματος.

Δομή της ενότητας Threading
Δομή της ενότητας Threading

Ακολουθεί μια λίστα με μερικές χρήσιμες λειτουργίες που ορίζονται σε αυτήν την ενότητα:

Όνομα συνάρτησης Descriptιόν
activeCount() Επιστρέφει τον αριθμό των Νήμα αντικείμενα που είναι ακόμα ζωντανά
τρέχονThread() Επιστρέφει το τρέχον αντικείμενο της κλάσης Thread.
απαριθμώ() Εμφανίζει όλα τα ενεργά αντικείμενα Thread.
isDaemon() Επιστρέφει true αν το νήμα είναι δαίμονας.
ειναι ΖΩΝΤΑΝΟΣ() Επιστρέφει true αν το νήμα είναι ακόμα ζωντανό.
Μέθοδοι κλάσης νημάτων
αρχή() Ξεκινά τη δραστηριότητα ενός νήματος. Πρέπει να κληθεί μόνο μία φορά για κάθε νήμα γιατί θα προκαλέσει σφάλμα χρόνου εκτέλεσης εάν καλείται πολλές φορές.
τρέξιμο() Αυτή η μέθοδος υποδηλώνει τη δραστηριότητα ενός νήματος και μπορεί να παρακαμφθεί από μια κλάση που επεκτείνει την κλάση Thread.
Συμμετοχή() Αποκλείει την εκτέλεση άλλου κώδικα μέχρι να τερματιστεί το νήμα στο οποίο κλήθηκε η μέθοδος join().

Backstory: The Thread Class

Πριν ξεκινήσετε την κωδικοποίηση προγραμμάτων πολλαπλών νημάτων χρησιμοποιώντας τη μονάδα νήματος, είναι σημαντικό να κατανοήσετε την κλάση Thread. Η κλάση νήματος είναι η κύρια κλάση που ορίζει το πρότυπο και τις λειτουργίες ενός νήματος στην python.

Ο πιο συνηθισμένος τρόπος για να δημιουργήσετε μια εφαρμογή python με πολλά νήματα είναι να δηλώσετε μια κλάση που επεκτείνει την κλάση Thread και παρακάμπτει τη μέθοδο run().

Η κλάση Thread, συνοπτικά, υποδηλώνει μια ακολουθία κώδικα που εκτελείται σε ξεχωριστό νήμα του ελέγχου.

Έτσι, όταν γράφετε μια εφαρμογή πολλαπλών νημάτων, θα κάνετε τα εξής:

  1. ορίστε μια κλάση που επεκτείνει την κλάση Thread
  2. Παράκαμψη του __init__ κατασκευαστής
  3. Παράκαμψη του τρέξιμο() μέθοδος

Μόλις κατασκευαστεί ένα αντικείμενο νήματος, το αρχή() μέθοδος μπορεί να χρησιμοποιηθεί για να ξεκινήσει η εκτέλεση αυτής της δραστηριότητας και το Συμμετοχή() Η μέθοδος μπορεί να χρησιμοποιηθεί για τον αποκλεισμό όλου του άλλου κώδικα μέχρι να ολοκληρωθεί η τρέχουσα δραστηριότητα.

Τώρα, ας δοκιμάσουμε να χρησιμοποιήσουμε την ενότητα threading για να εφαρμόσουμε το προηγούμενο παράδειγμά σας. Και πάλι, άναψε το δικό σου IDLE και πληκτρολογήστε τα εξής:

import time
import threading

class threadtester (threading.Thread):
    def __init__(self, id, name, i):
       threading.Thread.__init__(self)
       self.id = id
       self.name = name
       self.i = i
       
    def run(self):
       thread_test(self.name, self.i, 5)
       print ("%s has finished execution " %self.name)

def thread_test(name, wait, i):

    while i:
       time.sleep(wait)
       print ("Running %s \n" %name)
       i = i - 1

if __name__=="__main__":
    thread1 = threadtester(1, "First Thread", 1)
    thread2 = threadtester(2, "Second Thread", 2)
    thread3 = threadtester(3, "Third Thread", 3)

    thread1.start()
    thread2.start()
    thread3.start()

    thread1.join()
    thread2.join()
    thread3.join()

Αυτή θα είναι η έξοδος κατά την εκτέλεση του παραπάνω κώδικα:

Backstory: The Thread Class

ΚΩΔΙΚΟΣ ΕΞΗΓΗΣΗ

Backstory: The Thread Class

  1. Αυτό το μέρος είναι το ίδιο με το προηγούμενο παράδειγμά μας. Εδώ, εισάγετε την ενότητα χρόνου και νήματος που χρησιμοποιούνται για τον χειρισμό της εκτέλεσης και των καθυστερήσεων του Python νήματα.
  2. Σε αυτό το bit, δημιουργείτε μια κλάση που ονομάζεται threadtester, η οποία κληρονομεί ή επεκτείνει το Νήμα κλάση της ενότητας threading. Αυτός είναι ένας από τους πιο συνηθισμένους τρόπους δημιουργίας νημάτων στον python. Ωστόσο, θα πρέπει να παρακάμψετε μόνο τον κατασκευαστή και το τρέξιμο() μέθοδο στην εφαρμογή σας. Όπως μπορείτε να δείτε στο παραπάνω δείγμα κώδικα, το __init__ μέθοδος (κατασκευαστής) έχει παρακαμφθεί. Ομοίως, έχετε επίσης παρακάμψει το τρέξιμο() μέθοδος. Περιέχει τον κώδικα που θέλετε να εκτελέσετε μέσα σε ένα νήμα. Σε αυτό το παράδειγμα, έχετε καλέσει τη συνάρτηση thread_test().
  3. Αυτή είναι η μέθοδος thread_test() που παίρνει την τιμή του i ως όρισμα, το μειώνει κατά 1 σε κάθε επανάληψη και επαναλαμβάνει τον υπόλοιπο κώδικα έως ότου το i γίνει 0. Σε κάθε επανάληψη, εκτυπώνει το όνομα του νήματος που εκτελείται αυτήν τη στιγμή και αδράνει για δευτερόλεπτα αναμονής (το οποίο λαμβάνεται επίσης ως όρισμα ).
  4. thread1 = threadtester(1, “First Thread”, 1) Εδώ, δημιουργούμε ένα νήμα και περνάμε τις τρεις παραμέτρους που δηλώσαμε στο __init__. Η πρώτη παράμετρος είναι το id του νήματος, η δεύτερη παράμετρος είναι το όνομα του νήματος και η τρίτη παράμετρος είναι ο μετρητής, ο οποίος καθορίζει πόσες φορές πρέπει να τρέχει ο βρόχος while.
  5. thread2.start()T η μέθοδος έναρξης χρησιμοποιείται για την έναρξη της εκτέλεσης ενός νήματος. Εσωτερικά, η συνάρτηση start() καλεί τη μέθοδο run() της τάξης σας.
  6. thread3.join() Η μέθοδος join() μπλοκάρει την εκτέλεση άλλου κώδικα και περιμένει μέχρι να τελειώσει το νήμα στο οποίο ονομάστηκε.

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

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

Αδιέξοδα και συνθήκες αγώνα

Πριν μάθετε για τα αδιέξοδα και τις συνθήκες αγώνα, θα είναι χρήσιμο να κατανοήσετε μερικούς βασικούς ορισμούς που σχετίζονται με τον ταυτόχρονο προγραμματισμό:

  • Κρίσιμη ενότηταΕίναι ένα τμήμα κώδικα που έχει πρόσβαση ή τροποποιεί κοινές μεταβλητές και πρέπει να εκτελείται ως ατομική συναλλαγή.
  • Context SwitchΕίναι η διαδικασία που ακολουθεί μια CPU για να αποθηκεύσει την κατάσταση ενός νήματος πριν αλλάξει από τη μια εργασία στην άλλη, ώστε να μπορεί να συνεχιστεί από το ίδιο σημείο αργότερα.

Αδιέξοδα

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

Η δήλωση του προβλήματος για τους φιλοσόφους της εστίασης είναι η εξής:

Πέντε φιλόσοφοι κάθονται σε ένα στρογγυλό τραπέζι με πέντε πιάτα μακαρόνια (είδος ζυμαρικών) και πέντε πιρούνια, όπως φαίνεται στο διάγραμμα.

Τραπεζαρία Philosophers Πρόβλημα

Τραπεζαρία Philosophers Πρόβλημα

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

Επιπλέον, ένας φιλόσοφος πρέπει να πάρει τα δύο πιρούνια που βρίσκονται δίπλα του (δηλαδή, το αριστερό και το δεξί πιρούνι) πριν μπορέσει να φάει τα μακαρόνια. Το πρόβλημα του αδιεξόδου εμφανίζεται όταν και οι πέντε φιλόσοφοι σηκώνουν το δεξί πιρούνι τους ταυτόχρονα.

Εφόσον καθένας από τους φιλοσόφους έχει ένα πιρούνι, όλοι θα περιμένουν τους υπόλοιπους να βάλουν το πιρούνι τους κάτω. Ως αποτέλεσμα, κανένας από αυτούς δεν θα μπορεί να φάει μακαρόνια.

Ομοίως, σε ένα ταυτόχρονο σύστημα, εμφανίζεται ένα αδιέξοδο όταν διαφορετικά νήματα ή διεργασίες (φιλόσοφοι) προσπαθούν να αποκτήσουν τους κοινόχρηστους πόρους του συστήματος (forks) ταυτόχρονα. Ως αποτέλεσμα, καμία από τις διεργασίες δεν έχει την ευκαιρία να εκτελεστεί καθώς περιμένουν έναν άλλο πόρο που διατηρείται από κάποια άλλη διεργασία.

Συνθήκες Αγώνα

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

i=0; # a global variable
for x in range(100):
    print(i)
    i+=1;

Αν δημιουργήσετε n τον αριθμό των νημάτων που εκτελούν αυτόν τον κώδικα ταυτόχρονα, δεν μπορείτε να προσδιορίσετε την τιμή του i (η οποία μοιράζεται τα νήματα) όταν το πρόγραμμα ολοκληρώσει την εκτέλεση. Αυτό συμβαίνει επειδή σε ένα πραγματικό περιβάλλον πολλαπλών νημάτων, τα νήματα μπορεί να επικαλύπτονται και η τιμή του i που ανακτήθηκε και τροποποιήθηκε από ένα νήμα μπορεί να αλλάξει ενδιάμεσα όταν κάποιο άλλο νήμα αποκτήσει πρόσβαση σε αυτό.

Αυτές είναι οι δύο κύριες κατηγορίες προβλημάτων που μπορούν να προκύψουν σε μια πολυνηματική ή κατανεμημένη εφαρμογή python. Στην επόμενη ενότητα, θα μάθετε πώς να ξεπερνάτε αυτό το πρόβλημα συγχρονίζοντας νήματα.

Syncνήματα χρονισμού

Για την αντιμετώπιση των συνθηκών αγώνων, των αδιεξόδων και άλλων ζητημάτων που βασίζονται σε νήματα, η ενότητα threading παρέχει Κλειδαριά αντικείμενο. Η ιδέα είναι ότι όταν ένα νήμα θέλει πρόσβαση σε έναν συγκεκριμένο πόρο, αποκτά ένα κλείδωμα για αυτόν τον πόρο. Μόλις ένα νήμα κλειδώσει έναν συγκεκριμένο πόρο, κανένα άλλο νήμα δεν μπορεί να έχει πρόσβαση σε αυτόν μέχρι να απελευθερωθεί το κλείδωμα. Ως αποτέλεσμα, οι αλλαγές στον πόρο θα είναι ατομικές και οι συνθήκες φυλής θα αποτρέπονται.

Η κλειδαριά είναι ένα αρχέγονο συγχρονισμού χαμηλού επιπέδου που υλοποιείται από το __Νήμα μονάδα μέτρησης. Ανά πάσα στιγμή, μια κλειδαριά μπορεί να βρίσκεται σε μία από τις 2 καταστάσεις: κλειδωμένη or ακλείδωτος. Υποστηρίζει δύο μεθόδους:

  1. αποκτώ()Όταν η κατάσταση κλειδώματος είναι ξεκλείδωτη, η κλήση της μεθόδου αποκτήσει() θα αλλάξει την κατάσταση σε κλειδωμένη και θα επιστρέψει. Ωστόσο, εάν η κατάσταση είναι κλειδωμένη, η κλήση για να αποκτήσει() μπλοκάρεται μέχρι να κληθεί η μέθοδος release() από κάποιο άλλο νήμα.
  2. ελευθέρωση()Η μέθοδος release() χρησιμοποιείται για να ορίσετε την κατάσταση σε ξεκλείδωτη, δηλαδή, για να απελευθερώσετε μια κλειδαριά. Μπορεί να κληθεί με οποιοδήποτε νήμα, όχι απαραίτητα από αυτό που απέκτησε την κλειδαριά.

Ακολουθεί ένα παράδειγμα χρήσης κλειδαριών στις εφαρμογές σας. Άναψε το δικό σου IDLE και πληκτρολογήστε τα εξής:

import threading
lock = threading.Lock()

def first_function():
    for i in range(5):
        lock.acquire()
        print ('lock acquired')
        print ('Executing the first funcion')
        lock.release()

def second_function():
    for i in range(5):
        lock.acquire()
        print ('lock acquired')
        print ('Executing the second funcion')
        lock.release()

if __name__=="__main__":
    thread_one = threading.Thread(target=first_function)
    thread_two = threading.Thread(target=second_function)

    thread_one.start()
    thread_two.start()

    thread_one.join()
    thread_two.join()

Τώρα, πατήστε F5. Θα πρέπει να δείτε μια έξοδο όπως αυτή:

SyncΧρονίζοντας Νήματα

ΚΩΔΙΚΟΣ ΕΞΗΓΗΣΗ

SyncΧρονίζοντας Νήματα

  1. Εδώ, απλά δημιουργείτε μια νέα κλειδαριά καλώντας το threading.Lock() εργοστασιακή λειτουργία. Εσωτερικά, η Lock() επιστρέφει μια παρουσία της πιο αποτελεσματικής κλάσης Lock από σκυρόδεμα που διατηρείται από την πλατφόρμα.
  2. Στην πρώτη δήλωση, αποκτάτε το κλείδωμα καλώντας τη μέθοδο gain(). Όταν παραχωρηθεί η κλειδαριά, εκτυπώνετε «Κλείδωμα αποκτήθηκε» στην κονσόλα. Μόλις ολοκληρωθεί η εκτέλεση του κώδικα που θέλετε να εκτελεστεί το νήμα, απελευθερώνετε το κλείδωμα καλώντας τη μέθοδο release().

Η θεωρία είναι καλή, αλλά πώς ξέρετε ότι η κλειδαριά λειτούργησε πραγματικά; Εάν κοιτάξετε την έξοδο, θα δείτε ότι κάθε μία από τις εντολές εκτύπωσης εκτυπώνει ακριβώς μία γραμμή κάθε φορά. Θυμηθείτε ότι, σε ένα προηγούμενο παράδειγμα, τα αποτελέσματα από την εκτύπωση ήταν τυχαία επειδή πολλά νήματα είχαν πρόσβαση στη μέθοδο print() ταυτόχρονα. Εδώ, η λειτουργία εκτύπωσης καλείται μόνο μετά την απόκτηση του κλειδώματος. Έτσι, οι έξοδοι εμφανίζονται μία κάθε φορά και γραμμή προς γραμμή.

Εκτός από τις κλειδαριές, η python υποστηρίζει επίσης ορισμένους άλλους μηχανισμούς για το χειρισμό του συγχρονισμού νημάτων όπως αναφέρονται παρακάτω:

  1. RL Κλειδαριές
  2. Semaphores
  3. Όροι
  4. Εκδηλώσεις, και
  5. Εμπόδια

Global Interpreter Lock (και πώς να το αντιμετωπίσετε)

Πριν μπούμε στις λεπτομέρειες του GIL της python, ας ορίσουμε μερικούς όρους που θα είναι χρήσιμοι για την κατανόηση της επερχόμενης ενότητας:

  1. Κωδικός δεσμευμένος σε CPU: αναφέρεται σε οποιοδήποτε κομμάτι κώδικα που θα εκτελεστεί απευθείας από την CPU.
  2. Κώδικας δεσμευμένου εισόδου/εξόδου: αυτός μπορεί να είναι οποιοσδήποτε κωδικός που έχει πρόσβαση στο σύστημα αρχείων μέσω του λειτουργικού συστήματος
  3. CPython: είναι η αναφορά εκτέλεση of Python και μπορεί να περιγραφεί ως ο διερμηνέας γραμμένος σε C και Python (γλώσσα προγραμματισμού).

Σε τι είναι το GIL Python?

Global Interpreter Lock (GIL) στο python είναι ένα κλείδωμα διαδικασίας ή ένα mutex που χρησιμοποιείται κατά την επεξεργασία των διεργασιών. Διασφαλίζει ότι ένα νήμα μπορεί να έχει πρόσβαση σε έναν συγκεκριμένο πόρο τη φορά και επίσης αποτρέπει τη χρήση αντικειμένων και bytecodes ταυτόχρονα. Αυτό ωφελεί τα προγράμματα μονού νήματος σε μια αύξηση της απόδοσης. Το GIL σε python είναι πολύ απλό και εύκολο στην εφαρμογή.

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

Ένα από τα χαρακτηριστικά του Python είναι ότι χρησιμοποιεί ένα καθολικό κλείδωμα σε κάθε διεργασία διερμηνέα, πράγμα που σημαίνει ότι κάθε διεργασία αντιμετωπίζει τον ίδιο τον διερμηνέα python ως πόρο.

Για παράδειγμα, ας υποθέσουμε ότι έχετε γράψει ένα πρόγραμμα python το οποίο χρησιμοποιεί δύο νήματα για να εκτελέσει τις λειτουργίες CPU και 'I/O'. Όταν εκτελείτε αυτό το πρόγραμμα, συμβαίνει αυτό:

  1. Ο διερμηνέας python δημιουργεί μια νέα διαδικασία και δημιουργεί τα νήματα
  2. Όταν το νήμα-1 αρχίσει να τρέχει, θα αποκτήσει πρώτα το GIL και θα το κλειδώσει.
  3. Εάν το νήμα-2 θέλει να εκτελεστεί τώρα, θα πρέπει να περιμένει την απελευθέρωση του GIL ακόμα κι αν ένας άλλος επεξεργαστής είναι ελεύθερος.
  4. Τώρα, ας υποθέσουμε ότι το νήμα-1 περιμένει για μια λειτουργία I/O. Αυτή τη στιγμή, θα απελευθερώσει το GIL και το νήμα-2 θα το αποκτήσει.
  5. Μετά την ολοκλήρωση των επιλογών εισόδου/εξόδου, εάν το νήμα-1 θέλει να εκτελεστεί τώρα, θα πρέπει και πάλι να περιμένει την απελευθέρωση του GIL από το νήμα-2.

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

Αυτό είναι εντάξει σε έναν επεξεργαστή μονού πυρήνα, επειδή θα χρησιμοποιούσε το time slicing (δείτε την πρώτη ενότητα αυτού του σεμιναρίου) για να χειριστεί τα νήματα. Ωστόσο, στην περίπτωση πολυπύρηνων επεξεργαστών, μια συνάρτηση δεσμευμένη σε CPU που εκτελείται σε πολλαπλά νήματα θα έχει σημαντικό αντίκτυπο στην αποτελεσματικότητα του προγράμματος, καθώς στην πραγματικότητα δεν θα χρησιμοποιεί όλους τους διαθέσιμους πυρήνες ταυτόχρονα.

Γιατί χρειαζόταν το GIL;

Η CPython Ο συλλέκτης σκουπιδιών χρησιμοποιεί μια αποτελεσματική τεχνική διαχείρισης μνήμης γνωστή ως μέτρηση αναφοράς. Δείτε πώς λειτουργεί: Κάθε αντικείμενο στην python έχει έναν αριθμό αναφορών, ο οποίος αυξάνεται όταν εκχωρείται σε ένα νέο όνομα μεταβλητής ή προστίθεται σε ένα κοντέινερ (όπως πλειάδες, λίστες κ.λπ.). Ομοίως, το πλήθος αναφοράς μειώνεται όταν η αναφορά ξεφεύγει από το πεδίο εφαρμογής ή όταν καλείται η δήλωση del. Όταν το πλήθος αναφοράς ενός αντικειμένου φτάσει στο 0, συλλέγονται σκουπίδια και ελευθερώνεται η εκχωρημένη μνήμη.

Αλλά το πρόβλημα είναι ότι η μεταβλητή πλήθους αναφοράς είναι επιρρεπής σε συνθήκες φυλής όπως κάθε άλλη καθολική μεταβλητή. Για να λύσουν αυτό το πρόβλημα, οι προγραμματιστές της python αποφάσισαν να χρησιμοποιήσουν το παγκόσμιο κλείδωμα διερμηνέα. Η άλλη επιλογή ήταν να προστεθεί ένα κλείδωμα σε κάθε αντικείμενο που θα είχε ως αποτέλεσμα αδιέξοδα και αυξημένα έξοδα από τις κλήσεις buy() και release().

Ως εκ τούτου, το GIL είναι ένας σημαντικός περιορισμός για προγράμματα python πολλαπλών νημάτων που εκτελούν βαριές λειτουργίες συνδεδεμένες με την CPU (καθιστώντας τα ουσιαστικά μονονήμα). Εάν θέλετε να χρησιμοποιήσετε πολλαπλούς πυρήνες CPU στην εφαρμογή σας, χρησιμοποιήστε το πολυεπεξεργασία αντ' αυτού.

Σύνοψη

  • Python υποστηρίζει 2 modules για multithreading:
    1. __Νήμα ενότητα: Παρέχει μια υλοποίηση χαμηλού επιπέδου για νήμα και είναι ξεπερασμένη.
    2. μονάδα σπειρώματος: Παρέχει μια υλοποίηση υψηλού επιπέδου για multithreading και είναι το τρέχον πρότυπο.
  • Για να δημιουργήσετε ένα νήμα χρησιμοποιώντας τη μονάδα threading, πρέπει να κάνετε τα εξής:
    1. Δημιουργήστε μια κλάση που επεκτείνει το Νήμα τάξη.
    2. Παράκαμψη του κατασκευαστή του (__init__).
    3. Παρακάμψτε το τρέξιμο() μέθοδος.
    4. Δημιουργήστε ένα αντικείμενο αυτής της κλάσης.
  • Ένα νήμα μπορεί να εκτελεστεί καλώντας το αρχή() μέθοδος.
  • The Συμμετοχή() Η μέθοδος μπορεί να χρησιμοποιηθεί για τον αποκλεισμό άλλων νημάτων έως ότου αυτό το νήμα (αυτό στο οποίο κλήθηκε το join) ολοκληρώσει την εκτέλεση.
  • Μια συνθήκη κούρσας προκύπτει όταν πολλά νήματα έχουν πρόσβαση ή τροποποιούν έναν κοινόχρηστο πόρο ταυτόχρονα.
  • Μπορεί να αποφευχθεί με Syncνήματα χρονισμού.
  • Python υποστηρίζει 6 τρόπους συγχρονισμού νημάτων:
    1. Κλειδαριές
    2. RL Κλειδαριές
    3. Semaphores
    4. Όροι
    5. Εκδηλώσεις, και
    6. Εμπόδια
  • Οι κλειδαριές επιτρέπουν μόνο σε ένα συγκεκριμένο νήμα που έχει αποκτήσει την κλειδαριά να εισέλθει στο κρίσιμο τμήμα.
  • Μια κλειδαριά έχει 2 κύριες μεθόδους:
    1. αποκτώ(): Ορίζει την κατάσταση κλειδώματος σε κλειδωμένο. Εάν καλείται σε ένα κλειδωμένο αντικείμενο, αποκλείεται έως ότου ο πόρος είναι ελεύθερος.
    2. ελευθέρωση(): Ορίζει την κατάσταση κλειδώματος σε ξεκλείδωτη και επιστρέφει. Εάν καλείται σε ένα ξεκλείδωτο αντικείμενο, επιστρέφει ψευδές.
  • Το παγκόσμιο κλείδωμα διερμηνέα είναι ένας μηχανισμός μέσω του οποίου μόνο 1 CPython Η διαδικασία διερμηνέα μπορεί να εκτελεστεί κάθε φορά.
  • Χρησιμοποιήθηκε για να διευκολύνει τη λειτουργία μέτρησης αναφοράς του CPythonσκουπιδοσυλλέκτης του s.
  • Για να κάνετε Python εφαρμογές με βαριές λειτουργίες συνδεδεμένες με CPU, θα πρέπει να χρησιμοποιήσετε τη μονάδα πολλαπλής επεξεργασίας.