Η γλώσσα προγραμματισμού python σάς επιτρέπει να χρησιμοποιείτε πολλαπλές επεξεργασίες ή multithreading. Σε αυτό το σεμινάριο, θα μάθετε πώς να γράφετε εφαρμογές πολλαπλών νημάτων στο Python.
Τι είναι το νήμα;
Ένα νήμα είναι μια μονάδα επιμέλειας σε ταυτόχρονο προγραμματισμό. Το Multithreading είναι μια τεχνική που επιτρέπει σε έναν επεξεργαστή να εκτελεί πολλές εργασίες μιας διαδικασίας ταυτόχρονα. Αυτά τα νήματα μπορούν να εκτελεστούν μεμονωμένα ενώ μοιράζονται τους πόρους της διαδικασίας τους.
Τι είναι μια διαδικασία;
Μια διαδικασία είναι βασικά το πρόγραμμα σε εκτέλεση. Όταν ξεκινάτε μια εφαρμογή στον υπολογιστή σας (όπως πρόγραμμα περιήγησης ή πρόγραμμα επεξεργασίας κειμένου), το λειτουργικό σύστημα δημιουργεί μια διαδικασία.
Τι είναι το Multithreading στο Python;
Το multithreading στον προγραμματισμό Python είναι μια πολύ γνωστή τεχνική κατά την οποία πολλαπλά νήματα σε μια διαδικασία μοιράζονται τον χώρο δεδομένων τους με το κύριο νήμα που καθιστά την ανταλλαγή πληροφοριών και την επικοινωνία εντός νήματος εύκολη και αποτελεσματική. Τα νήματα είναι ελαφρύτερα από τις διαδικασίες. Τα πολλαπλά νήματα ενδέχεται να εκτελούνται μεμονωμένα ενώ μοιράζονται τους πόρους της διαδικασίας τους. Ο σκοπός του multithreading είναι να εκτελεί πολλαπλές εργασίες και κελιά λειτουργίας ταυτόχρονα.
Τι είναι η Πολυεπεξεργασία;
Η πολλαπλή επεξεργασία σάς επιτρέπει να εκτελείτε ταυτόχρονα πολλές άσχετες διαδικασίες. Αυτές οι διαδικασίες δεν μοιράζονται τους πόρους τους και επικοινωνούν μέσω IPC.
Python Multithreading εναντίον Multiprocessing
Για να κατανοήσετε τις διαδικασίες και τα νήματα, εξετάστε αυτό το σενάριο: Ένα αρχείο .exe στον υπολογιστή σας είναι ένα πρόγραμμα. Όταν το ανοίγετε, το λειτουργικό σύστημα το φορτώνει στη μνήμη και η CPU το εκτελεί. Η παρουσία του προγράμματος που εκτελείται τώρα ονομάζεται διαδικασία.
Κάθε διαδικασία θα έχει 2 βασικά συστατικά:
- Ο κώδικας
- Τα δεδομένα
Τώρα, μια διαδικασία μπορεί να περιέχει ένα ή περισσότερα υπο-μέρη που ονομάζονται νήματα. Αυτό εξαρτάται από την αρχιτεκτονική του λειτουργικού συστήματος. Μπορείτε να σκεφτείτε ένα νήμα ως τμήμα της διαδικασίας που μπορεί να εκτελεστεί ξεχωριστά από το λειτουργικό σύστημα.
Με άλλα λόγια, είναι μια ροή οδηγιών που μπορεί να εκτελείται ανεξάρτητα από το λειτουργικό σύστημα. Τα νήματα σε μία μόνο διαδικασία μοιράζονται τα δεδομένα αυτής της διαδικασίας και έχουν σχεδιαστεί για να συνεργάζονται για τη διευκόλυνση του παραλληλισμού.
Σε αυτό το σεμινάριο, θα μάθετε,
- Τι είναι το νήμα;
- Τι είναι μια διαδικασία;
- Τι είναι το Multithreading;
- Τι είναι η Πολυεπεξεργασία;
- Python Multithreading εναντίον Multiprocessing
- Γιατί να χρησιμοποιήσετε το Multithreading;
- Python MultiThreading
- Οι ενότητες Thread and Threading
- Η ενότητα νήματος
- Η ενότητα Threading
- Αδιέξοδα και συνθήκες αγώνα
- Συγχρονισμός νημάτων
- Τι είναι το GIL;
- Γιατί χρειαζόταν το GIL;
Γιατί να χρησιμοποιήσετε το Multithreading;
Το Multithreading σάς επιτρέπει να αναλύσετε μια εφαρμογή σε πολλές δευτερεύουσες εργασίες και να εκτελέσετε αυτές τις εργασίες ταυτόχρονα. Εάν χρησιμοποιείτε σωστά το multithreading, η ταχύτητα, η απόδοση και η απόδοση της εφαρμογής σας μπορούν να βελτιωθούν.
Python MultiThreading
Η Python υποστηρίζει κατασκευές τόσο για πολυεπεξεργασία όσο και για πολλαπλά νήματα. Σε αυτό το σεμινάριο, εστιάζετε κυρίως στην εφαρμογή πολυνηματικών εφαρμογών με το python. Υπάρχουν δύο κύριες ενότητες που μπορούν να χρησιμοποιηθούν για το χειρισμό νημάτων στο Python:
- Η ενότητα νημάτων , και
- Η ενότητα σπειρώματος
Ωστόσο, στο python, υπάρχει επίσης κάτι που ονομάζεται παγκόσμια κλειδαριά διερμηνέα (GIL). Δεν επιτρέπει μεγάλο κέρδος απόδοσης και μπορεί ακόμη και να μειώσει την απόδοση ορισμένων πολυνηματικών εφαρμογών. Θα μάθετε τα πάντα για τις επόμενες ενότητες αυτού του σεμιναρίου.
Οι ενότητες Thread and Threading
Οι δύο ενότητες για τις οποίες θα μάθετε σε αυτό το σεμινάριο είναι η ενότητα νημάτων και η ενότητα νημάτων .
Ωστόσο, η ενότητα νήματος έχει από καιρό καταργηθεί. Ξεκινώντας με το Python 3, έχει χαρακτηριστεί ως ξεπερασμένο και είναι προσβάσιμο μόνο ως __thread για συμβατότητα προς τα πίσω.
Θα πρέπει να χρησιμοποιήσετε τη μονάδα νήματος υψηλότερου επιπέδου για εφαρμογές που σκοπεύετε να αναπτύξετε. Η ενότητα νήματος έχει καλυφθεί εδώ μόνο για εκπαιδευτικούς σκοπούς.
Η ενότητα νήματος
Η σύνταξη για τη δημιουργία ενός νέου νήματος χρησιμοποιώντας αυτήν την ενότητα έχει ως εξής:
thread.start_new_thread(function_name, arguments)
Εντάξει, τώρα έχετε καλύψει τη βασική θεωρία για να ξεκινήσετε την κωδικοποίηση. Ανοίξτε λοιπόν το IDLE ή ένα σημειωματάριο και πληκτρολογήστε τα εξής:
import timeimport _threaddef thread_test(name, wait):i = 0while i <= 3:time.sleep(wait)print("Running %s\n" %name)i = i + 1print("%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 ως όρισμα.
Αυτό θα δημιουργήσει ένα νέο νήμα για τη συνάρτηση που περνάτε ως όρισμα και θα αρχίσει να την εκτελεί. Σημειώστε ότι μπορείτε να το αντικαταστήσετε (νήμα _ test) με οποιαδήποτε άλλη λειτουργία που θέλετε να εκτελέσετε ως νήμα.
Η ενότητα Threading
Αυτή η ενότητα είναι η υψηλού επιπέδου εφαρμογή του νήματος σε python και το de facto πρότυπο για τη διαχείριση εφαρμογών πολλαπλών νημάτων. Παρέχει ένα ευρύ φάσμα χαρακτηριστικών σε σύγκριση με την ενότητα νήματος.
Ακολουθεί μια λίστα με μερικές χρήσιμες λειτουργίες που ορίζονται σε αυτήν την ενότητα:
Όνομα συνάρτησης | Περιγραφή |
ενεργός αριθμός () | Επιστρέφει την καταμέτρηση αντικειμένων νημάτων που είναι ακόμα ζωντανά |
currentThread () | Επιστρέφει το τρέχον αντικείμενο της κλάσης νήματος. |
απαριθμώ() | Εμφανίζει όλα τα ενεργά αντικείμενα νήματος. |
isDaemon () | Επιστρέφει true αν το νήμα είναι δαίμονας. |
ειναι ΖΩΝΤΑΝΟΣ() | Επιστρέφει αληθές εάν το νήμα είναι ακόμα ζωντανό. |
Μέθοδοι κλάσης νημάτων | |
αρχή() | Ξεκινά τη δραστηριότητα ενός νήματος. Πρέπει να καλείται μόνο μία φορά για κάθε νήμα, διότι θα προκαλέσει σφάλμα χρόνου εκτέλεσης, εάν καλείται πολλές φορές. |
τρέξιμο() | Αυτή η μέθοδος υποδηλώνει τη δραστηριότητα ενός νήματος και μπορεί να παρακαμφθεί από μια κλάση που επεκτείνει την κλάση νήματος. |
Συμμετοχή() | Αποκλείει την εκτέλεση άλλου κώδικα έως ότου τερματιστεί το νήμα στο οποίο κλήθηκε η μέθοδος join (). |
Backstory: Η τάξη των νημάτων
Πριν ξεκινήσετε την κωδικοποίηση πολυνηματικών προγραμμάτων χρησιμοποιώντας την ενότητα νημάτων, είναι σημαντικό να κατανοήσετε σχετικά με την κλάση νημάτων. Η κλάση νημάτων είναι η κύρια κλάση που καθορίζει το πρότυπο και τις λειτουργίες ενός νήματος στο python.
Ο πιο συνηθισμένος τρόπος για να δημιουργήσετε μια εφαρμογή πολλαπλών νημάτων python είναι να δηλώσετε μια κλάση που επεκτείνει την κλάση Thread και παρακάμπτει τη μέθοδο εκτέλεσης ().
Η κλάση νήματος, συνοπτικά, σημαίνει μια ακολουθία κώδικα που εκτελείται σε ξεχωριστό νήμα ελέγχου.
Έτσι, όταν γράφετε μια εφαρμογή πολλαπλών νημάτων, θα κάνετε τα εξής:
- ορίστε μια κλάση που επεκτείνει την κλάση νήματος
- Παράκαμψη του κατασκευαστή __init__
- Παράκαμψη της μεθόδου εκτέλεσης ()
Μόλις γίνει ένα αντικείμενο νήματος, η μέθοδος start () μπορεί να χρησιμοποιηθεί για να ξεκινήσει η εκτέλεση αυτής της δραστηριότητας και η μέθοδος join () μπορεί να χρησιμοποιηθεί για τον αποκλεισμό όλων των άλλων κωδικών έως ότου ολοκληρωθεί η τρέχουσα δραστηριότητα.
Τώρα, ας προσπαθήσουμε να χρησιμοποιήσουμε την ενότητα σπειρώματος για να εφαρμόσουμε το προηγούμενο παράδειγμά σας. Και πάλι, ενεργοποιήστε το IDLE σας και πληκτρολογήστε τα εξής:
import timeimport threadingclass threadtester (threading.Thread):def __init__(self, id, name, i):threading.Thread.__init__(self)self.id = idself.name = nameself.i = idef 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 - 1if __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, η οποία κληρονομεί ή επεκτείνει την κλάση Thread της ενότητας threading Αυτός είναι ένας από τους πιο συνηθισμένους τρόπους δημιουργίας νημάτων στο python. Ωστόσο, θα πρέπει να παρακάμψετε μόνο τον κατασκευαστή και τη μέθοδο εκτέλεσης () στην εφαρμογή σας. Όπως μπορείτε να δείτε στο παραπάνω δείγμα κώδικα, η μέθοδος __init__ (κατασκευαστής) έχει παρακαμφθεί.
Ομοίως, έχετε επίσης παρακάμψει τη μέθοδο run () . Περιέχει τον κώδικα που θέλετε να εκτελέσετε μέσα σε ένα νήμα. Σε αυτό το παράδειγμα, έχετε καλέσει τη συνάρτηση thread_test ().
- Αυτή είναι η μέθοδος thread_test () που παίρνει την τιμή του i ως όρισμα, τη μειώνει κατά 1 σε κάθε επανάληψη και περνάει τον υπόλοιπο κώδικα μέχρι να γίνω 0. Σε κάθε επανάληψη, εκτυπώνει το όνομα του τρέχοντος νήματος που εκτελείται και κοιμάται για δευτερόλεπτα αναμονής (το οποίο θεωρείται επίσης ως επιχείρημα).
- thread1 = threadtester (1, "Πρώτο νήμα", 1)
Εδώ, δημιουργούμε ένα νήμα και περνάμε τις τρεις παραμέτρους που δηλώσαμε στο __init__. Η πρώτη παράμετρος είναι το αναγνωριστικό του νήματος, η δεύτερη παράμετρος είναι το όνομα του νήματος και η τρίτη παράμετρος είναι ο μετρητής, ο οποίος καθορίζει πόσες φορές πρέπει να εκτελείται ο βρόχος while.
- thread2.start ()
Η μέθοδος εκκίνησης χρησιμοποιείται για να ξεκινήσει η εκτέλεση ενός νήματος. Εσωτερικά, η συνάρτηση start () καλεί τη μέθοδο run () της τάξης σας.
- νήμα3.join ()
Η μέθοδος join () αποκλείει την εκτέλεση άλλου κώδικα και περιμένει μέχρι να ολοκληρωθεί το νήμα στο οποίο ονομάστηκε.
Όπως γνωρίζετε ήδη, τα νήματα που βρίσκονται στην ίδια διαδικασία έχουν πρόσβαση στη μνήμη και τα δεδομένα αυτής της διαδικασίας. Ως αποτέλεσμα, εάν περισσότερα από ένα νήματα προσπαθούν να αλλάξουν ή να αποκτήσουν πρόσβαση στα δεδομένα ταυτόχρονα, ενδέχεται να εισέλθουν σφάλματα.
Στην επόμενη ενότητα, θα δείτε τα διάφορα είδη επιπλοκών που μπορούν να εμφανιστούν όταν τα νήματα έχουν πρόσβαση σε δεδομένα και σε κρίσιμη ενότητα χωρίς να ελέγχουν τις υπάρχουσες συναλλαγές πρόσβασης.
Αδιέξοδα και συνθήκες αγώνα
Πριν μάθετε για τα αδιέξοδα και τις συνθήκες αγώνα, θα ήταν χρήσιμο να κατανοήσετε μερικούς βασικούς ορισμούς που σχετίζονται με τον ταυτόχρονο προγραμματισμό:
- Κρίσιμο τμήμα
Είναι ένα κομμάτι κώδικα που έχει πρόσβαση ή τροποποιεί κοινόχρηστες μεταβλητές και πρέπει να εκτελεστεί ως ατομική συναλλαγή.
- Διακόπτης περιβάλλοντος
Είναι η διαδικασία που ακολουθεί μια CPU για να αποθηκεύσει την κατάσταση ενός νήματος πριν αλλάξει από τη μία εργασία στην άλλη έτσι ώστε να μπορεί να συνεχιστεί από το ίδιο σημείο αργότερα.
Αδιέξοδα
Τα αδιέξοδα είναι το πιο φοβισμένο πρόβλημα που αντιμετωπίζουν οι προγραμματιστές όταν γράφουν ταυτόχρονες / πολυνηματικές εφαρμογές στο python. Ο καλύτερος τρόπος για να κατανοήσετε τα αδιέξοδα είναι να χρησιμοποιήσετε το κλασικό πρόβλημα παραδείγματος της επιστήμης των υπολογιστών που είναι γνωστό ως Πρόβλημα Φιλόσοφων.
Η δήλωση προβλήματος για τους φιλοσόφους φαγητού έχει ως εξής:
Πέντε φιλόσοφοι κάθονται σε μια στρογγυλή τράπεζα με πέντε πλάκες μακαρονιών (ένας τύπος ζυμαρικών) και πέντε πιρούνια, όπως φαίνεται στο διάγραμμα.
Ανά πάσα στιγμή, ένας φιλόσοφος πρέπει είτε να τρώει είτε να σκέφτεται.
Επιπλέον, ένας φιλόσοφος πρέπει να πάρει τα δύο πιρούνια δίπλα του (δηλαδή, τα αριστερά και τα δεξιά πιρούνια) προτού να φάει τα μακαρόνια. Το πρόβλημα του αδιεξόδου εμφανίζεται όταν και οι πέντε φιλόσοφοι παίρνουν τα δεξιά τους πιρούνια ταυτόχρονα.
Δεδομένου ότι κάθε ένας από τους φιλόσοφους έχει ένα πιρούνι, όλοι θα περιμένουν τους άλλους να βάλουν το πιρούνι τους. Ως αποτέλεσμα, κανένας από αυτούς δεν θα μπορεί να φάει μακαρόνια.
Ομοίως, σε ένα ταυτόχρονο σύστημα, εμφανίζεται αδιέξοδο όταν διαφορετικά νήματα ή διαδικασίες (φιλόσοφοι) προσπαθούν να αποκτήσουν τους πόρους του κοινού συστήματος (πιρούνια) ταυτόχρονα. Ως αποτέλεσμα, καμία από τις διαδικασίες δεν έχει την ευκαιρία να εκτελεστεί καθώς περιμένουν έναν άλλο πόρο που κατέχει κάποια άλλη διαδικασία.
Συνθήκες αγώνα
Μια συνθήκη αγώνα είναι μια ανεπιθύμητη κατάσταση ενός προγράμματος που εμφανίζεται όταν ένα σύστημα εκτελεί δύο ή περισσότερες λειτουργίες ταυτόχρονα. Για παράδειγμα, θεωρήστε αυτό το απλό για βρόχο:
i=0; # a global variablefor x in range(100):print(i)i+=1;
Εάν δημιουργήσετε n αριθμό νημάτων που εκτελούν αυτόν τον κώδικα ταυτόχρονα, δεν μπορείτε να προσδιορίσετε την τιμή του i (το οποίο κοινοποιείται από τα νήματα) όταν το πρόγραμμα ολοκληρώσει την εκτέλεση. Αυτό συμβαίνει επειδή σε ένα πραγματικό περιβάλλον πολλαπλών νημάτων, τα νήματα μπορούν να αλληλεπικαλύπτονται και η τιμή του i που ανακτήθηκε και τροποποιήθηκε από ένα νήμα μπορεί να αλλάξει ενδιάμεσα όταν κάποιο άλλο νήμα έχει πρόσβαση σε αυτό.
Αυτές είναι οι δύο κύριες κατηγορίες προβλημάτων που μπορούν να προκύψουν σε μια εφαρμογή πολλαπλών νημάτων ή κατανεμημένων python. Στην επόμενη ενότητα, θα μάθετε πώς να ξεπεράσετε αυτό το πρόβλημα συγχρονίζοντας νήματα.
Συγχρονισμός νημάτων
Για να αντιμετωπίσει τις συνθήκες αγώνα, τα αδιέξοδα και άλλα θέματα που βασίζονται σε νήματα, η ενότητα σπειρώματος παρέχει το αντικείμενο Κλείδωμα . Η ιδέα είναι ότι όταν ένα νήμα θέλει πρόσβαση σε έναν συγκεκριμένο πόρο, αποκτά μια κλειδαριά για αυτόν τον πόρο. Μόλις ένα νήμα κλειδώσει έναν συγκεκριμένο πόρο, κανένα άλλο νήμα δεν μπορεί να έχει πρόσβαση μέχρι να αποδεσμευτεί το κλείδωμα. Ως αποτέλεσμα, οι αλλαγές στον πόρο θα είναι ατομικές και οι συνθήκες αγώνα θα αποφευχθούν.
Το κλείδωμα είναι ένα πρωτόγονο συγχρονισμού χαμηλού επιπέδου που εφαρμόζεται από την ενότητα __thread . Ανά πάσα στιγμή, μια κλειδαριά μπορεί να βρίσκεται σε μία από τις 2 καταστάσεις: κλειδωμένη ή ξεκλείδωτη. Υποστηρίζει δύο μεθόδους:
- αποκτώ()
Όταν ξεκλειδωθεί η κατάσταση κλειδώματος, η κλήση της μεθόδου απόκτησης () θα αλλάξει την κατάσταση σε κλειδωμένη και θα επιστρέψει. Ωστόσο, εάν η κατάσταση είναι κλειδωμένη, η κλήση για απόκτηση () αποκλείεται έως ότου η μέθοδος απελευθέρωσης () καλείται από κάποιο άλλο νήμα
- ελευθέρωση()
Η μέθοδος Release () χρησιμοποιείται για να ρυθμίσει την κατάσταση σε ξεκλείδωτο, δηλαδή για να απελευθερώσει ένα κλείδωμα. Μπορεί να καλείται από οποιοδήποτε νήμα, όχι απαραίτητα από αυτό που απέκτησε την κλειδαριά.
Ακολουθεί ένα παράδειγμα χρήσης κλειδαριών στις εφαρμογές σας. Ενεργοποιήστε το IDLE σας και πληκτρολογήστε τα εξής:
import threadinglock = 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. Θα πρέπει να δείτε μια έξοδο ως εξής:
ΕΠΕΞΗΓΗΣΗ ΚΩΔΙΚΟΥ
- Εδώ, δημιουργείτε απλά ένα νέο κλείδωμα καλώντας τη λειτουργία νήματος. Κλείδωμα () εργοστασιακή. Εσωτερικά, το Lock () επιστρέφει μια παρουσία της πιο αποτελεσματικής κλάσης Lock Lock που διατηρείται από την πλατφόρμα.
- Στην πρώτη δήλωση, αποκτάτε το κλείδωμα καλώντας τη μέθοδο απόκτησης (). Όταν παραχωρηθεί το κλείδωμα, εκτυπώνετε "κλειδαριά που αποκτήθηκε" στην κονσόλα. Μόλις ολοκληρωθεί η εκτέλεση όλου του κώδικα που θέλετε να εκτελείται το νήμα, αφήστε το κλείδωμα καλώντας τη μέθοδο απελευθέρωσης ().
Η θεωρία είναι μια χαρά, αλλά πώς ξέρετε ότι η κλειδαριά λειτούργησε πραγματικά; Αν κοιτάξετε την έξοδο, θα δείτε ότι κάθε μία από τις δηλώσεις εκτύπωσης εκτυπώνει ακριβώς μία γραμμή κάθε φορά. Θυμηθείτε ότι, σε ένα προηγούμενο παράδειγμα, οι έξοδοι από την εκτύπωση είναι τυχαίες επειδή πολλά νήματα είχαν πρόσβαση στη μέθοδο εκτύπωσης () ταυτόχρονα Εδώ, η λειτουργία εκτύπωσης καλείται μόνο μετά την απόκτηση του κλειδώματος. Έτσι, οι έξοδοι εμφανίζονται μία κάθε φορά και κάθε γραμμή.
Εκτός από τις κλειδαριές, η python υποστηρίζει επίσης μερικούς άλλους μηχανισμούς για τον χειρισμό του συγχρονισμού νημάτων όπως παρατίθεται παρακάτω:
- Κλειδαριές
- Σηματοφόροι
- Συνθήκες
- Εκδηλώσεις και
- Εμπόδια
Global Interpreter Lock (και πώς να το χειριστείτε)
Πριν μπείτε στις λεπτομέρειες του GIL του python, ας καθορίσουμε μερικούς όρους που θα είναι χρήσιμοι για την κατανόηση της επερχόμενης ενότητας:
- Κωδικός συνδεδεμένος με CPU: αναφέρεται σε οποιοδήποτε κομμάτι κώδικα που θα εκτελεστεί απευθείας από τη CPU.
- Κωδικός που συνδέεται με I / O: μπορεί να είναι οποιοσδήποτε κωδικός που έχει πρόσβαση στο σύστημα αρχείων μέσω του λειτουργικού συστήματος
- CPython: είναι η εφαρμογή αναφοράς του Python και μπορεί να περιγραφεί ως διερμηνέας γραμμένος στα C και Python (γλώσσα προγραμματισμού).
Τι είναι το GIL στο Python;
Το Global Interpreter Lock (GIL) στο python είναι ένα κλείδωμα διεργασίας ή ένα mutex που χρησιμοποιείται ενώ ασχολείται με τις διαδικασίες. Διασφαλίζει ότι ένα νήμα μπορεί να έχει πρόσβαση σε έναν συγκεκριμένο πόρο τη φορά και επίσης αποτρέπει τη χρήση αντικειμένων και bytecodes ταυτόχρονα. Αυτό ωφελεί τα προγράμματα με ένα νήμα σε αύξηση της απόδοσης. Το GIL στο python είναι πολύ απλό και εύκολο στην εφαρμογή.
Ένα κλείδωμα μπορεί να χρησιμοποιηθεί για να βεβαιωθείτε ότι μόνο ένα νήμα έχει πρόσβαση σε έναν συγκεκριμένο πόρο σε μια δεδομένη στιγμή.
Ένα από τα χαρακτηριστικά του Python είναι ότι χρησιμοποιεί ένα παγκόσμιο κλείδωμα σε κάθε διαδικασία διερμηνέα, πράγμα που σημαίνει ότι κάθε διαδικασία αντιμετωπίζει τον ίδιο τον διερμηνέα python ως πόρο.
Για παράδειγμα, ας υποθέσουμε ότι έχετε γράψει ένα πρόγραμμα python που χρησιμοποιεί δύο νήματα για να εκτελέσει τόσο τις CPU όσο και τις λειτουργίες «I / O». Όταν εκτελείτε αυτό το πρόγραμμα, αυτό συμβαίνει:
- Ο διερμηνέας python δημιουργεί μια νέα διαδικασία και γεννά τα νήματα
- Όταν το νήμα-1 αρχίσει να τρέχει, θα αποκτήσει πρώτα το GIL και θα το κλειδώσει.
- Εάν το νήμα-2 θέλει να εκτελέσει τώρα, θα πρέπει να περιμένει να κυκλοφορήσει το GIL ακόμη και αν ένας άλλος επεξεργαστής είναι δωρεάν.
- Τώρα, ας υποθέσουμε ότι το νήμα-1 περιμένει μια λειτουργία I / O. Προς το παρόν, θα κυκλοφορήσει το GIL και το νήμα-2 θα το αποκτήσει.
- Αφού ολοκληρώσετε τις λειτουργίες I / O, εάν το νήμα-1 θέλει να εκτελέσει τώρα, θα πρέπει πάλι να περιμένει να κυκλοφορήσει το GIL από το νήμα-2.
Λόγω αυτού, μόνο ένα νήμα μπορεί να έχει πρόσβαση στον διερμηνέα ανά πάσα στιγμή, πράγμα που σημαίνει ότι θα υπάρχει μόνο ένα νήμα που θα εκτελεί κώδικα python σε μια δεδομένη χρονική στιγμή.
Αυτό είναι εντάξει σε έναν επεξεργαστή ενός πυρήνα, επειδή θα χρησιμοποιούσε χρονικό περιορισμό (δείτε την πρώτη ενότητα αυτού του σεμιναρίου) για να χειριστείτε τα νήματα. Ωστόσο, στην περίπτωση πολλαπλών πυρήνων επεξεργαστών, μια λειτουργία συνδεδεμένη με CPU που εκτελείται σε πολλά νήματα θα έχει σημαντικό αντίκτυπο στην αποδοτικότητα του προγράμματος, καθώς δεν θα χρησιμοποιεί στην πραγματικότητα όλους τους διαθέσιμους πυρήνες ταυτόχρονα.
Γιατί χρειαζόταν το GIL;
Ο συλλέκτης σκουπιδιών CPython χρησιμοποιεί μια αποτελεσματική τεχνική διαχείρισης μνήμης γνωστή ως καταμέτρηση αναφοράς. Δείτε πώς λειτουργεί: Κάθε αντικείμενο στο python έχει έναν αριθμό αναφοράς, ο οποίος αυξάνεται όταν αντιστοιχίζεται σε ένα νέο όνομα μεταβλητής ή προστίθεται σε ένα κοντέινερ (όπως πλειάδες, λίστες κ.λπ.). Παρομοίως, ο αριθμός αναφοράς μειώνεται όταν η αναφορά εξαντλείται ή όταν καλείται η δήλωση del. Όταν ο αριθμός αναφοράς ενός αντικειμένου φτάσει στο 0, συλλέγεται σκουπίδια και απελευθερώνεται η εκχωρημένη μνήμη.
Αλλά το πρόβλημα είναι ότι η μεταβλητή μέτρησης αναφοράς είναι επιρρεπής σε συνθήκες αγώνα όπως οποιαδήποτε άλλη καθολική μεταβλητή. Για να λύσουν αυτό το πρόβλημα, οι προγραμματιστές του python αποφάσισαν να χρησιμοποιήσουν το παγκόσμιο κλείδωμα διερμηνέα. Η άλλη επιλογή ήταν να προσθέσετε ένα κλείδωμα σε κάθε αντικείμενο που θα είχε ως αποτέλεσμα αδιέξοδα και αυξημένη επιβάρυνση από την απόκτηση () και την απελευθέρωση () κλήσεων.
Επομένως, το GIL είναι ένας σημαντικός περιορισμός για προγράμματα πολλαπλών νημάτων python που εκτελούν βαριές λειτουργίες συνδεδεμένες με CPU (καθιστώντας τα αποτελεσματικά μονόστροφα). Εάν θέλετε να χρησιμοποιήσετε πολλούς πυρήνες CPU στην εφαρμογή σας, χρησιμοποιήστε τη μονάδα πολλαπλής επεξεργασίας .
Περίληψη
- Η Python υποστηρίζει 2 λειτουργικές μονάδες για multithreading:
- __thread module: Παρέχει εφαρμογή χαμηλού επιπέδου για το νήμα και είναι ξεπερασμένη.
- threading module : Παρέχει εφαρμογή υψηλού επιπέδου για multithreading και αποτελεί το τρέχον πρότυπο.
- Για να δημιουργήσετε ένα νήμα χρησιμοποιώντας την ενότητα νημάτων, πρέπει να κάνετε τα εξής:
- Δημιουργήστε μια τάξη που επεκτείνει την τάξη νήματος .
- Παράκαμψη του κατασκευαστή του (__init__).
- Παράκαμψη της μεθόδου run () .
- Δημιουργήστε ένα αντικείμενο αυτής της τάξης.
- Ένα νήμα μπορεί να εκτελεστεί καλώντας τη μέθοδο έναρξης () .
- Η μέθοδος join () μπορεί να χρησιμοποιηθεί για να μπλοκάρει άλλα νήματα έως ότου ολοκληρωθεί η εκτέλεση αυτού του νήματος (αυτό στο οποίο κλήθηκε η ένωση).
- Μια κατάσταση αγώνα εμφανίζεται όταν πολλά νήματα έχουν πρόσβαση ή τροποποιούν έναν κοινόχρηστο πόρο ταυτόχρονα.
- Μπορεί να αποφευχθεί με συγχρονισμό νημάτων.
- Η Python υποστηρίζει 6 τρόπους συγχρονισμού νημάτων:
- Κλειδαριές
- Κλειδαριές
- Σηματοφόροι
- Συνθήκες
- Εκδηλώσεις και
- Εμπόδια
- Κλειδαριές επιτρέπουν μόνο ένα συγκεκριμένο νήμα που έχει αποκτήσει το κλείδωμα να εισέλθει στην κρίσιμη ενότητα.
- Το Lock έχει 2 κύριες μεθόδους:
- αποκτούν () : Ρυθμίζει την κατάσταση κλειδώματος σε κλειδωμένη. Εάν καλείται σε ένα κλειδωμένο αντικείμενο, μπλοκάρει έως ότου ο πόρος είναι ελεύθερος.
- release () : Ρυθμίζει την κατάσταση κλειδώματος σε ξεκλείδωτο και επιστρέφει. Εάν κληθεί σε ένα ξεκλειδωμένο αντικείμενο, επιστρέφει ψευδές.
- Το παγκόσμιο κλείδωμα διερμηνέων είναι ένας μηχανισμός μέσω του οποίου μπορεί να εκτελεστεί μόνο μία διαδικασία διερμηνέα 1 CPython κάθε φορά.
- Χρησιμοποιήθηκε για να διευκολύνει τη λειτουργία καταμέτρησης αναφοράς του συλλέκτη απορριμμάτων CPythons.
- Για να δημιουργήσετε εφαρμογές Python με βαριές λειτουργίες συνδεδεμένες με CPU, θα πρέπει να χρησιμοποιήσετε τη μονάδα πολλαπλής επεξεργασίας.