Multithreading in Python με Παράδειγμα: Μάθετε το GIL σε Python
⚡ Έξυπνη Σύνοψη
Multithreading in Python Εκτελεί πολλά νήματα μέσα σε μία διεργασία, έτσι ώστε να μοιράζονται μνήμη και να εργάζονται ταυτόχρονα. Η μονάδα νημάτων δημιουργεί και διαχειρίζεται αυτά τα νήματα, ενώ το Κλείδωμα Παγκόσμιου Διερμηνέα περιορίζει τον πραγματικό παραλληλισμό, καθιστώντας την τεχνική ιδανική για εργασίες που συνδέονται με είσοδο/έξοδο.

The Python Η γλώσσα προγραμματισμού σάς επιτρέπει να χρησιμοποιείτε πολυεπεξεργασία ή πολυνηματοποίηση. Σε αυτό το σεμινάριο, θα μάθετε πώς να γράφετε πολυνηματικές εφαρμογές σε Python.
Τι είναι ένα νήμα;
Ένα νήμα είναι μια μονάδα εκτέλεσης στον ταυτόχρονο προγραμματισμό. Η πολυνηματική επεξεργασία είναι μια τεχνική που επιτρέπει σε μια CPU να εκτελεί πολλές εργασίες μιας διεργασίας ταυτόχρονα. Αυτά τα νήματα μπορούν να εκτελούνται μεμονωμένα ενώ μοιράζονται τους πόρους της διεργασίας τους.
Τι είναι μια Διαδικασία;
Μια διεργασία είναι ουσιαστικά το πρόγραμμα που εκτελείται. Όταν ξεκινάτε μια εφαρμογή στον υπολογιστή σας (όπως ένα πρόγραμμα περιήγησης ή ένα πρόγραμμα επεξεργασίας κειμένου), το λειτουργικό σύστημα δημιουργεί ένα διαδικασία.
Τι είναι το Multithreading μέσα Python?
Multithreading in Python Ο προγραμματισμός είναι μια πολύ γνωστή τεχνική στην οποία πολλά νήματα σε μια διεργασία μοιράζονται τον χώρο δεδομένων τους με το κύριο νήμα, γεγονός που καθιστά την κοινή χρήση πληροφοριών και την επικοινωνία εντός των νημάτων εύκολη και αποτελεσματική. Τα νήματα είναι ελαφρύτερα από τις διεργασίες. Πολλαπλά νήματα μπορούν να εκτελούνται μεμονωμένα ενώ μοιράζονται τους πόρους της διεργασίας τους. Ο σκοπός της πολυνηματοποίησης είναι η εκτέλεση πολλαπλών εργασιών και συναρτήσεων ταυτόχρονα.
Τι είναι η Πολυεπεξεργασία;
Πολυεπεξεργασία σας επιτρέπει να εκτελείτε πολλές άσχετες διεργασίες ταυτόχρονα. Αυτές οι διεργασίες δεν μοιράζονται τους πόρους τους και επικοινωνούν μέσω IPC.
Python Multithreading έναντι Multiprocessing
Για να κατανοήσετε τις διεργασίες και τα νήματα, σκεφτείτε το εξής σενάριο: Ένα αρχείο .exe στον υπολογιστή σας είναι ένα πρόγραμμα. Όταν το ανοίγετε, το λειτουργικό σύστημα το φορτώνει στη μνήμη και η CPU το εκτελεί. Η παρουσία του προγράμματος που εκτελείται αυτήν τη στιγμή ονομάζεται διεργασία.
Κάθε διαδικασία έχει δύο βασικά στοιχεία:
- The Code
- Τα δεδομένα
Τώρα, μια διαδικασία μπορεί να περιέχει ένα ή περισσότερα υποτμήματα που ονομάζονται νήματα. Αυτό εξαρτάται από την αρχιτεκτονική του λειτουργικού συστήματος. Μπορείτε να σκεφτείτε ένα νήμα ως ένα τμήμα της διεργασίας που μπορεί να εκτελεστεί ξεχωριστά από το λειτουργικό σύστημα.
Με άλλα λόγια, είναι μια ροή εντολών που μπορούν να εκτελεστούν ανεξάρτητα από το λειτουργικό σύστημα. Τα νήματα μέσα σε μια ενιαία διεργασία μοιράζονται τα δεδομένα αυτής της διεργασίας και έχουν σχεδιαστεί για να συνεργάζονται για να διευκολύνουν την παραλληλία.
Γιατί να χρησιμοποιήσετε το Multithreading;
Το Multithreading σάς επιτρέπει να αναλύσετε μια εφαρμογή σε πολλαπλές υπο-εργασίες και να εκτελέσετε αυτές τις εργασίες ταυτόχρονα. Εάν χρησιμοποιείτε σωστά το multithreading, η ταχύτητα, η απόδοση και η απόδοση της εφαρμογής σας μπορούν να βελτιωθούν.
Python Πολλαπλών νημάτων
Python υποστηρίζει δομές τόσο για πολλαπλή επεξεργασία όσο και για πολλαπλά νήματα. Σε αυτό το σεμινάριο, θα εστιάσετε κυρίως στην υλοποίηση πολυήμα εφαρμογές με PythonΥπάρχουν δύο κύριες ενότητες που μπορούν να χρησιμοποιηθούν για τη διαχείριση νημάτων σε Python:
- The νήμα ενότητα, και
- 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 για να εκτελέσετε το πρόγραμμα. Εάν όλα έγιναν σωστά, αυτό είναι το αποτέλεσμα που θα πρέπει να δείτε:
Θα μάθετε περισσότερα για τις συνθήκες του αγώνα και πώς να τις χειριστείτε στις επόμενες ενότητες.
ΚΩΔΙΚΟΣ ΕΞΗΓΗΣΗ
- Αυτές οι εντολές εισάγουν την ενότητα χρόνου και νήματος, οι οποίες χρησιμοποιούνται για τον χειρισμό της εκτέλεσης και της καθυστέρησης του Python νήματα.
- Εδώ, έχετε ορίσει μια συνάρτηση που ονομάζεται thread_test, που θα κληθεί από το start_new_thread μέθοδος. Η συνάρτηση εκτελεί έναν βρόχο while για τέσσερις επαναλήψεις και εμφανίζει το όνομα του νήματος που την κάλεσε. Μόλις ολοκληρωθεί η επανάληψη, εμφανίζει ένα μήνυμα που λέει ότι το νήμα έχει ολοκληρώσει την εκτέλεση.
- Αυτή είναι η κύρια ενότητα του προγράμματός σας. Εδώ, απλά καλείτε το start_new_thread μέθοδος με το thread_test συνάρτηση ως όρισμα. Αυτό θα δημιουργήσει ένα νέο νήμα για τη συνάρτηση που περνάτε ως όρισμα και θα ξεκινήσει η εκτέλεσή της. Σημειώστε ότι μπορείτε να αντικαταστήσετε αυτήν τη συνάρτηση (thread_test) με οποιαδήποτε άλλη συνάρτηση θέλετε να εκτελέσετε ως νήμα.
Το Threading Module
Αυτή η ενότητα είναι η υλοποίηση υψηλού επιπέδου της δημιουργίας νημάτων σε Python και το de facto πρότυπο για τη διαχείριση εφαρμογών πολλαπλών νημάτων. Παρέχει ένα ευρύ φάσμα χαρακτηριστικών σε σύγκριση με την ενότητα νημάτων.
Δομή της ενότητας Threading
Ακολουθεί μια λίστα με μερικές χρήσιμες λειτουργίες που ορίζονται σε αυτήν την ενότητα:
| Όνομα συνάρτησης | Περιγραφή |
|---|---|
| activeCount() | Επιστρέφει τον αριθμό των Νήμα αντικείμενα που είναι ακόμα ζωντανά. |
| τρέχονThread() | Επιστρέφει το τρέχον αντικείμενο της κλάσης Thread. |
| απαριθμώ() | Εμφανίζει όλα τα ενεργά αντικείμενα Thread. |
| isDaemon() | Επιστρέφει true αν το νήμα είναι δαίμονας. |
| ειναι ΖΩΝΤΑΝΟΣ() | Επιστρέφει true αν το νήμα είναι ακόμα ζωντανό. |
| Μέθοδοι κλάσης νημάτων | |
| αρχή() | Ξεκινά τη δραστηριότητα ενός νήματος. Πρέπει να κληθεί μόνο μία φορά για κάθε νήμα γιατί θα προκαλέσει σφάλμα χρόνου εκτέλεσης εάν καλείται πολλές φορές. |
| τρέξιμο() | Αυτή η μέθοδος υποδηλώνει τη δραστηριότητα ενός νήματος και μπορεί να παρακαμφθεί από μια κλάση που επεκτείνει την κλάση Thread. |
| Συμμετοχή() | Αποκλείει την εκτέλεση άλλου κώδικα μέχρι να τερματιστεί το νήμα στο οποίο κλήθηκε η μέθοδος join(). |
Backstory: The Thread Class
Πριν ξεκινήσετε να κωδικοποιείτε προγράμματα πολλαπλών νημάτων χρησιμοποιώντας την ενότητα threading, είναι σημαντικό να κατανοήσετε την κλάση Thread. Η κλάση thread είναι η κύρια κλάση που ορίζει το πρότυπο και τις λειτουργίες ενός thread στο Python.
Ο πιο συνηθισμένος τρόπος δημιουργίας πολυνηματικής Python Η εφαρμογή είναι να δηλώσει μια κλάση που επεκτείνει την κλάση Thread και παρακάμπτει τη μέθοδο run() της.
Η κλάση Thread, συνοπτικά, υποδηλώνει μια ακολουθία κώδικα που εκτελείται σε ξεχωριστό νήμα του ελέγχου.
Έτσι, όταν γράφετε μια εφαρμογή πολλαπλών νημάτων, θα κάνετε τα εξής:
- ορίστε μια κλάση που επεκτείνει την κλάση Thread
- Παράκαμψη του __init__ κατασκευαστής
- Παράκαμψη του τρέξιμο() μέθοδος
Μόλις κατασκευαστεί ένα αντικείμενο νήματος, το αρχή() η μέθοδος μπορεί να χρησιμοποιηθεί για να ξεκινήσει η εκτέλεση αυτής της δραστηριότητας, και η Συμμετοχή() Η μέθοδος μπορεί να χρησιμοποιηθεί για τον αποκλεισμό όλου του άλλου κώδικα μέχρι να ολοκληρωθεί η τρέχουσα δραστηριότητα.
Τώρα, ας δοκιμάσουμε να χρησιμοποιήσουμε την ενότητα 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()
Αυτή θα είναι η έξοδος κατά την εκτέλεση του παραπάνω κώδικα:
ΚΩΔΙΚΟΣ ΕΞΗΓΗΣΗ
- Αυτό το μέρος είναι το ίδιο με το προηγούμενο παράδειγμά μας. Εδώ, εισάγετε την ενότητα χρόνου και νήματος, οι οποίες χρησιμοποιούνται για τον χειρισμό της εκτέλεσης και των καθυστερήσεων του Python νήματα.
- Σε αυτό το bit, δημιουργείτε μια κλάση που ονομάζεται threadtester, η οποία κληρονομεί ή επεκτείνει το Νήμα κλάση της ενότητας threading. Αυτός είναι ένας από τους πιο συνηθισμένους τρόπους δημιουργίας threads σε PythonΩστόσο, θα πρέπει να παρακάμψετε μόνο τον κατασκευαστή και το τρέξιμο() μέθοδο στην εφαρμογή σας. Όπως μπορείτε να δείτε στο παραπάνω δείγμα κώδικα, το __init__ μέθοδος (κατασκευαστής) έχει παρακαμφθεί. Ομοίως, έχετε επίσης παρακάμψει το τρέξιμο() μέθοδος. Περιέχει τον κώδικα που θέλετε να εκτελέσετε μέσα σε ένα νήμα. Σε αυτό το παράδειγμα, έχετε καλέσει τη συνάρτηση thread_test().
- Αυτή είναι η μέθοδος thread_test(), η οποία λαμβάνει την τιμή i ως όρισμα, το μειώνει κατά 1 σε κάθε επανάληψη και επαναλαμβάνει τον υπόλοιπο κώδικα μέχρι το i να γίνει 0. Σε κάθε επανάληψη, εκτυπώνει το όνομα του τρέχοντος εκτελούμενου νήματος και κοιμάται για δευτερόλεπτα αναμονής (το οποίο λαμβάνεται επίσης ως όρισμα).
- thread1 = threadtester(1, “First Thread”, 1) Εδώ, δημιουργούμε ένα νήμα και περνάμε τις τρεις παραμέτρους που δηλώσαμε στο __init__. Η πρώτη παράμετρος είναι το id του νήματος, η δεύτερη παράμετρος είναι το όνομα του νήματος και η τρίτη παράμετρος είναι ο μετρητής, ο οποίος καθορίζει πόσες φορές πρέπει να τρέχει ο βρόχος while.
- thread2.start() Η μέθοδος start χρησιμοποιείται για την έναρξη της εκτέλεσης ενός νήματος. Εσωτερικά, η συνάρτηση start() καλεί τη μέθοδο run() της κλάσης σας.
- thread3.join() Η μέθοδος join() μπλοκάρει την εκτέλεση άλλου κώδικα και περιμένει μέχρι να τελειώσει το νήμα στο οποίο ονομάστηκε.
Όπως ήδη γνωρίζετε, τα νήματα που βρίσκονται στην ίδια διεργασία έχουν πρόσβαση στη μνήμη και τα δεδομένα αυτής της διεργασίας. Ως αποτέλεσμα, εάν περισσότερα από ένα νήματα προσπαθήσουν να αλλάξουν ή να έχουν πρόσβαση στα δεδομένα ταυτόχρονα, ενδέχεται να εμφανιστούν σφάλματα.
Στην επόμενη ενότητα, θα δείτε τα διαφορετικά είδη επιπλοκών που μπορούν να εμφανιστούν όταν τα νήματα έχουν πρόσβαση σε δεδομένα και στην κρίσιμη ενότητα χωρίς να ελέγξουν για υπάρχουσες συναλλαγές πρόσβασης.
Αδιέξοδα και συνθήκες αγώνα
Πριν μάθετε για τα αδιέξοδα και τις συνθήκες ανταγωνισμού, θα ήταν χρήσιμο να κατανοήσετε μερικούς βασικούς ορισμούς που σχετίζονται με τον ταυτόχρονο προγραμματισμό:
- Κρίσιμο τμήμα: Είναι ένα τμήμα κώδικα που έχει πρόσβαση ή τροποποιεί κοινόχρηστες μεταβλητές και πρέπει να εκτελεστεί ως ατομική συναλλαγή.
- Αλλαγή περιβάλλοντος: Είναι η διαδικασία που ακολουθεί μια CPU για να αποθηκεύσει την κατάσταση ενός νήματος πριν αλλάξει από τη μία εργασία στην άλλη, ώστε να μπορεί να συνεχιστεί από το ίδιο σημείο αργότερα.
Αδιέξοδα
Αδιέξοδα είναι το πιο ανησυχητικό πρόβλημα που αντιμετωπίζουν οι προγραμματιστές όταν γράφουν ταυτόχρονες/πολυνηματικές εφαρμογές σε PythonΟ καλύτερος τρόπος για να κατανοήσετε τα αδιέξοδα είναι χρησιμοποιώντας το κλασικό παράδειγμα προβλήματος της επιστήμης των υπολογιστών, γνωστό ως Τραπεζαρία Philosophers Πρόβλημα.
Η δήλωση του προβλήματος για τους φιλοσόφους της εστίασης είναι η εξής:
Πέντε φιλόσοφοι κάθονται σε ένα στρογγυλό τραπέζι με πέντε πιάτα σπαγγέτι (ένα είδος ζυμαρικού) και πέντε πιρούνια, όπως φαίνεται στο διάγραμμα.
Τραπεζαρία Philosophers Πρόβλημα
Σε κάθε δεδομένη στιγμή, ένας φιλόσοφος πρέπει είτε να τρώει είτε να σκέφτεται.
Επιπλέον, ένας φιλόσοφος πρέπει να πάρει τα δύο πιρούνια που βρίσκονται δίπλα του (δηλαδή, το αριστερό και το δεξί πιρούνι) πριν μπορέσει να φάει τα μακαρόνια. Το πρόβλημα του αδιεξόδου εμφανίζεται όταν και οι πέντε φιλόσοφοι σηκώνουν το δεξί πιρούνι τους ταυτόχρονα.
Εφόσον καθένας από τους φιλοσόφους έχει ένα πιρούνι, όλοι θα περιμένουν τους υπόλοιπους να βάλουν το πιρούνι τους κάτω. Ως αποτέλεσμα, κανένας από αυτούς δεν θα μπορεί να φάει μακαρόνια.
Ομοίως, σε ένα ταυτόχρονο σύστημα, εμφανίζεται ένα αδιέξοδο όταν διαφορετικά νήματα ή διεργασίες (φιλόσοφοι) προσπαθούν να αποκτήσουν τους κοινόχρηστους πόρους του συστήματος (forks) ταυτόχρονα. Ως αποτέλεσμα, καμία από τις διεργασίες δεν έχει την ευκαιρία να εκτελεστεί καθώς περιμένουν έναν άλλο πόρο που διατηρείται από κάποια άλλη διεργασία.
Συνθήκες Αγώνα
Μια συνθήκη κούρσας είναι μια ανεπιθύμητη κατάσταση ενός προγράμματος που εμφανίζεται όταν ένα σύστημα εκτελεί δύο ή περισσότερες λειτουργίες ταυτόχρονα. Για παράδειγμα, σκεφτείτε αυτόν τον απλό βρόχο for:
i=0; # a global variable for x in range(100): print(i) i+=1;
Αν δημιουργήσετε n Ανεξάρτητα από τον αριθμό των νημάτων που εκτελούν αυτόν τον κώδικα ταυτόχρονα, δεν μπορείτε να προσδιορίσετε την τιμή του i (η οποία χρησιμοποιείται από τα νήματα) όταν ολοκληρωθεί η εκτέλεση του προγράμματος. Αυτό συμβαίνει επειδή σε ένα πραγματικό περιβάλλον πολλαπλών νημάτων, τα νήματα μπορούν να επικαλύπτονται και η τιμή του i που ανακτήθηκε και τροποποιήθηκε από ένα νήμα μπορεί να αλλάξει ενδιάμεσα όταν κάποιο άλλο νήμα έχει πρόσβαση σε αυτό.
Αυτές είναι οι δύο κύριες κατηγορίες προβλημάτων που μπορούν να προκύψουν σε ένα πολυνηματικό ή κατανεμημένο σύστημα. Python εφαρμογή. Στην επόμενη ενότητα, θα μάθετε πώς να ξεπεράσετε αυτό το πρόβλημα συγχρονίζοντας νήματα.
Syncνήματα χρονισμού
Για την αντιμετώπιση των συνθηκών αγώνων, των αδιεξόδων και άλλων ζητημάτων που βασίζονται σε νήματα, η ενότητα threading παρέχει Κλειδαριά αντικείμενο. Η ιδέα είναι ότι όταν ένα νήμα θέλει πρόσβαση σε έναν συγκεκριμένο πόρο, αποκτά ένα κλείδωμα για αυτόν τον πόρο. Μόλις ένα νήμα κλειδώσει έναν συγκεκριμένο πόρο, κανένα άλλο νήμα δεν μπορεί να έχει πρόσβαση σε αυτόν μέχρι να απελευθερωθεί το κλείδωμα. Ως αποτέλεσμα, οι αλλαγές στον πόρο θα είναι ατομικές και οι συνθήκες φυλής θα αποτρέπονται.
Η κλειδαριά είναι ένα αρχέγονο συγχρονισμού χαμηλού επιπέδου που υλοποιείται από το _νήμα ενότητα. Σε οποιαδήποτε δεδομένη στιγμή, μια κλειδαριά μπορεί να βρίσκεται σε μία από τις δύο καταστάσεις: κλειδωμένη or ακλείδωτος. Υποστηρίζει δύο μεθόδους:
- αποκτώ(): Όταν η κατάσταση κλειδώματος είναι ξεκλείδωτη, η κλήση της μεθόδου acquire() θα αλλάξει την κατάσταση σε κλειδωμένη και θα επιστρέψει. Ωστόσο, εάν η κατάσταση είναι κλειδωμένη, η κλήση της μεθόδου acquire() μπλοκάρεται μέχρι να κληθεί η μέθοδος release() από κάποιο άλλο νήμα.
- ελευθέρωση(): Η μέθοδος 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. Θα πρέπει να δείτε μια έξοδο όπως αυτή:
ΚΩΔΙΚΟΣ ΕΞΗΓΗΣΗ
- Εδώ, απλά δημιουργείτε μια νέα κλειδαριά καλώντας το threading.Lock() εργοστασιακή λειτουργία. Εσωτερικά, η Lock() επιστρέφει μια παρουσία της πιο αποτελεσματικής κλάσης Lock από σκυρόδεμα που διατηρείται από την πλατφόρμα.
- Στην πρώτη δήλωση, αποκτάτε το κλείδωμα καλώντας τη μέθοδο gain(). Όταν παραχωρηθεί η κλειδαριά, εκτυπώνετε «Κλείδωμα αποκτήθηκε» στην κονσόλα. Μόλις ολοκληρωθεί η εκτέλεση του κώδικα που θέλετε να εκτελεστεί το νήμα, απελευθερώνετε το κλείδωμα καλώντας τη μέθοδο release().
Η θεωρία είναι καλή, αλλά πώς ξέρετε ότι το κλείδωμα λειτούργησε πραγματικά; Αν κοιτάξετε την έξοδο, θα δείτε ότι κάθε μία από τις εντολές print εκτυπώνει ακριβώς μία γραμμή κάθε φορά. Θυμηθείτε ότι, σε ένα προηγούμενο παράδειγμα, οι έξοδοι από την print ήταν τυχαίες επειδή πολλά νήματα είχαν πρόσβαση στη μέθοδο print() ταυτόχρονα. Εδώ, η συνάρτηση print καλείται μόνο αφού αποκτηθεί το κλείδωμα. Έτσι, οι έξοδοι εμφανίζονται μία κάθε φορά και γραμμή προς γραμμή.
Εκτός από τις κλειδαριές, Python υποστηρίζει επίσης ορισμένους άλλους μηχανισμούς για τη διαχείριση του συγχρονισμού νημάτων, όπως αναφέρονται παρακάτω:
- RL Κλειδαριές
- Semaphores
- Όροι
- Εκδηλώσεις, και
- Εμπόδια
Global Interpreter Lock (και πώς να το αντιμετωπίσετε)
Πριν μπούμε στις λεπτομέρειες του Python's GIL, ας ορίσουμε μερικούς όρους που θα είναι χρήσιμοι για την κατανόηση της επόμενης ενότητας:
- Κώδικας που συνδέεται με την CPU: αναφέρεται σε οποιοδήποτε κομμάτι κώδικα που θα εκτελεστεί απευθείας από την CPU.
- Κώδικας I/O-bound: αυτός μπορεί να είναι οποιοσδήποτε κώδικας που έχει πρόσβαση στο σύστημα αρχείων μέσω του λειτουργικού συστήματος.
- CPython: είναι η αναφορά εκτέλεση of Python και μπορεί να περιγραφεί ως ο διερμηνέας γραμμένος σε C και Python (γλώσσα προγραμματισμού).
Σε τι είναι το GIL Python?
Global Interpreter Lock (GIL) in Python είναι ένα κλείδωμα διεργασίας ή ένα mutex που χρησιμοποιείται κατά την επεξεργασία των διεργασιών. Εξασφαλίζει ότι ένα νήμα μπορεί να έχει πρόσβαση σε έναν συγκεκριμένο πόρο κάθε φορά και επίσης αποτρέπει τη χρήση αντικειμένων και bytecodes ταυτόχρονα. Αυτό ωφελεί τα προγράμματα με ένα μόνο νήμα με αύξηση της απόδοσης. GIL σε Python είναι πολύ απλό και εύκολο στην εφαρμογή.
Μια κλειδαριά μπορεί να χρησιμοποιηθεί για να βεβαιωθείτε ότι μόνο ένα νήμα έχει πρόσβαση σε έναν συγκεκριμένο πόρο τη δεδομένη στιγμή.
Ένα από τα χαρακτηριστικά του Python είναι ότι χρησιμοποιεί ένα καθολικό κλείδωμα σε κάθε διεργασία διερμηνέα, πράγμα που σημαίνει ότι κάθε διεργασία αντιμετωπίζει το Python ο ίδιος ο διερμηνέας ως πόρος.
Για παράδειγμα, ας υποθέσουμε ότι έχετε γράψει ένα Python πρόγραμμα που χρησιμοποιεί δύο νήματα για να εκτελέσει λειτουργίες CPU και 'Εισόδου/Εξόδου'. Όταν εκτελείτε αυτό το πρόγραμμα, συμβαίνει το εξής:
- The Python Ο διερμηνέας δημιουργεί μια νέα διεργασία και δημιουργεί τα νήματα.
- Όταν το νήμα-1 αρχίσει να τρέχει, θα αποκτήσει πρώτα το GIL και θα το κλειδώσει.
- Εάν το νήμα-2 θέλει να εκτελεστεί τώρα, θα πρέπει να περιμένει την απελευθέρωση του GIL ακόμα κι αν ένας άλλος επεξεργαστής είναι ελεύθερος.
- Τώρα, ας υποθέσουμε ότι το νήμα-1 περιμένει για μια λειτουργία I/O. Αυτή τη στιγμή, θα απελευθερώσει το GIL και το νήμα-2 θα το αποκτήσει.
- Μετά την ολοκλήρωση των επιλογών εισόδου/εξόδου, εάν το νήμα-1 θέλει να εκτελεστεί τώρα, θα πρέπει και πάλι να περιμένει την απελευθέρωση του GIL από το νήμα-2.
Λόγω αυτού, μόνο ένα νήμα μπορεί να έχει πρόσβαση στον διερμηνέα ανά πάσα στιγμή, πράγμα που σημαίνει ότι θα υπάρχει μόνο ένα νήμα που θα εκτελείται. Python κώδικα σε μια δεδομένη χρονική στιγμή.
Αυτό είναι εντάξει σε έναν επεξεργαστή ενός πυρήνα, επειδή θα χρησιμοποιούσε χρονική τομή (δείτε την πρώτη ενότητα αυτού του σεμιναρίου) για τον χειρισμό των νημάτων. Ωστόσο, στην περίπτωση των επεξεργαστών πολλαπλών πυρήνων, μια συνάρτηση που συνδέεται με την CPU και εκτελείται σε πολλαπλά νήματα θα έχει σημαντικό αντίκτυπο στην απόδοση του προγράμματος, καθώς στην πραγματικότητα δεν θα χρησιμοποιεί όλους τους διαθέσιμους πυρήνες ταυτόχρονα.
Γιατί χρειαζόταν το GIL;
Η CPython Ο συλλέκτης απορριμμάτων χρησιμοποιεί μια αποτελεσματική τεχνική διαχείρισης μνήμης γνωστή ως καταμέτρηση αναφορών. Δείτε πώς λειτουργεί: Κάθε αντικείμενο στο Python έχει έναν αριθμό αναφορών, ο οποίος αυξάνεται όταν αντιστοιχίζεται σε ένα νέο όνομα μεταβλητής ή προστίθεται σε ένα κοντέινερ (όπως πλειάδες, λίστες κ.λπ.). Ομοίως, ο αριθμός αναφορών μειώνεται όταν η αναφορά βγαίνει εκτός εμβέλειας ή όταν καλείται η εντολή del. Όταν ο αριθμός αναφορών ενός αντικειμένου φτάσει στο 0, συλλέγεται άχρηστο περιεχόμενο και η εκχωρημένη μνήμη ελευθερώνεται.
Αλλά το πρόβλημα είναι ότι η μεταβλητή αναφοράς count είναι επιρρεπής σε συνθήκες φυλής όπως κάθε άλλη καθολική μεταβλητή. Για να λύσουν αυτό το πρόβλημα, οι προγραμματιστές του Python αποφάσισε να χρησιμοποιήσει το κλείδωμα καθολικού διερμηνέα. Η άλλη επιλογή ήταν να προσθέσει ένα κλείδωμα σε κάθε αντικείμενο, κάτι που θα είχε ως αποτέλεσμα αδιέξοδα και αυξημένη επιβάρυνση από τις κλήσεις acquire() και release().
Επομένως, το GIL αποτελεί σημαντικό περιορισμό για πολυνηματικές Python προγράμματα που εκτελούν βαριές λειτουργίες που συνδέονται με την CPU (ουσιαστικά καθιστώντας τα single-threaded). Εάν θέλετε να χρησιμοποιήσετε πολλαπλούς πυρήνες CPU στην εφαρμογή σας, χρησιμοποιήστε το πολυεπεξεργασία αντ' αυτού.








