Python Threading: An Introduction – grtechpc.org

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

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

Ας αρχίσουμε.

Διαδικασίες εναντίον νημάτων: Ποιες είναι οι διαφορές;

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

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

Μπορεί να είναι οτιδήποτε – ένα σενάριο Python ή ένα πρόγραμμα περιήγησης ιστού όπως το Chrome σε μια εφαρμογή τηλεδιάσκεψης. Εάν εκκινήσετε τη Διαχείριση εργασιών στο μηχάνημά σας και μεταβείτε στο Performance –> CPU, θα μπορείτε να δείτε τις διεργασίες και τα νήματα που εκτελούνται αυτήν τη στιγμή στους πυρήνες της CPU.

Κατανόηση Διαδικασιών και Νημάτων

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

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

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

Σε μια CPU με N πυρήνες, N διεργασίες μπορούν να εκτελεστούν παράλληλα την ίδια χρονική στιγμή. Ωστόσο, δύο νήματα της ίδιας διαδικασίας δεν μπορούν ποτέ να εκτελεστούν παράλληλα- αλλά μπορούν να εκτελεστούν ταυτόχρονα. Θα ασχοληθούμε με την έννοια της ταυτότητος έναντι του παραλληλισμού στην επόμενη ενότητα.

Με βάση όσα μάθαμε μέχρι τώρα, ας συνοψίσουμε τις διαφορές μεταξύ μιας διαδικασίας και ενός νήματος.

FeatureProcessThreadMemoryΑποκλειστική μνήμη Κοινόχρηστη μνήμηΤρόπος εκτέλεσηςΠαράλληλη, ταυτόχρονη Ταυτόχρονη; αλλά όχι παράλληληΕκτέλεση που χειρίζεται το Operating SystemCPython Interpreter

Multithreading στην Python

Στην Python, το Global Interpreter Lock (GIL) διασφαλίζει ότι μόνο ένα νήμα μπορεί να αποκτήσει το κλείδωμα και να εκτελεστεί ανά πάσα στιγμή. Όλα τα νήματα πρέπει να αποκτήσουν αυτό το κλείδωμα για να τρέξουν. Αυτό διασφαλίζει ότι μόνο ένα μεμονωμένο νήμα μπορεί να εκτελεστεί – σε οποιαδήποτε δεδομένη χρονική στιγμή – και αποφεύγεται η ταυτόχρονη multithreading.

  Διορθώστε το σφάλμα Xbox One 0x80a40019

Για παράδειγμα, θεωρήστε δύο νήματα, t1 και t2, της ίδιας διαδικασίας. Επειδή τα νήματα μοιράζονται τα ίδια δεδομένα όταν το t1 διαβάζει μια συγκεκριμένη τιμή k, το t2 μπορεί να τροποποιήσει την ίδια τιμή k. Αυτό μπορεί να οδηγήσει σε αδιέξοδα και ανεπιθύμητα αποτελέσματα. Αλλά μόνο ένα από τα νήματα μπορεί να αποκτήσει την κλειδαριά και να τρέξει σε οποιαδήποτε περίπτωση. Επομένως, το GIL διασφαλίζει επίσης την ασφάλεια του νήματος.

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

Concurrency vs Parallelism: An Overview

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

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

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

Για παράδειγμα, τα νήματα που συνδέονται με I/O συχνά περιμένουν για λειτουργίες I/O: ανάγνωση στην είσοδο χρήστη, ανάγνωση βάσης δεδομένων και λειτουργίες αρχείων. Κατά τη διάρκεια αυτού του χρόνου αναμονής, μπορεί να απελευθερώσει την κλειδαριά έτσι ώστε να μπορεί να τρέξει το άλλο νήμα. Ο χρόνος αναμονής μπορεί επίσης να είναι μια απλή λειτουργία, όπως ο ύπνος για n δευτερόλεπτα.

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

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

Python Threading Module: First Steps

Η Python αποστέλλεται με μια λειτουργική μονάδα νήματος που μπορείτε να εισαγάγετε στο σενάριο Python.

import threading

Για να δημιουργήσετε ένα αντικείμενο νήματος στην Python, μπορείτε να χρησιμοποιήσετε τον κατασκευαστή Thread: threading.Thread(…). Αυτή είναι η γενική σύνταξη που αρκεί για τις περισσότερες υλοποιήσεις νημάτων:

threading.Thread(target=...,args=...)

Εδώ,

  • στόχος είναι το όρισμα λέξης-κλειδιού που υποδηλώνει μια Python δυνατότητα κλήσης
  • Το args είναι η πλειάδα των ορισμάτων που δέχεται ο στόχος.
  Πώς να εμφανίσετε τα κρυφά πλωτά πάνελ απόδοσης του Mac

Θα χρειαστείτε Python 3.x για να εκτελέσετε τα παραδείγματα κώδικα σε αυτό το σεμινάριο. Κατεβάστε τον κώδικα και ακολουθήστε τον.

Πώς να ορίσετε και να εκτελέσετε νήματα στην Python

Ας ορίσουμε ένα νήμα που εκτελεί μια συνάρτηση προορισμού.

Η συνάρτηση στόχος είναι some_func.

import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
print(threading.active_count())

Ας αναλύσουμε τι κάνει το παραπάνω απόσπασμα κώδικα:

  • Εισάγει το threading και το time modules.
  • Η συνάρτηση some_func έχει περιγραφικές δηλώσεις print() και περιλαμβάνει μια λειτουργία αναστολής λειτουργίας για δύο δευτερόλεπτα: time.sleep(n) κάνει τη συνάρτηση σε αδράνεια για n δευτερόλεπτα.
  • Στη συνέχεια, ορίζουμε ένα νήμα νήμα_1 με στόχο ως some_func. threading.Thread(target=…) δημιουργεί ένα αντικείμενο νήματος.
  • Σημείωση: Καθορίστε το όνομα της συνάρτησης και όχι μια κλήση συνάρτησης. χρησιμοποιήστε some_func και όχι some_func().
  • Η δημιουργία ενός αντικειμένου νήματος δεν ξεκινάει ένα νήμα. καλώντας τη μέθοδο start() στο αντικείμενο νήματος κάνει.
  • Για να λάβουμε τον αριθμό των ενεργών νημάτων, χρησιμοποιούμε τη συνάρτηση active_count().

Το σενάριο Python εκτελείται στο κύριο νήμα και δημιουργούμε ένα άλλο νήμα (thread1) για να εκτελέσουμε τη συνάρτηση some_func, ώστε ο αριθμός ενεργών νημάτων να είναι δύο, όπως φαίνεται στην έξοδο:

# Output
Running some_func...
2
Finished running some_func.

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

Αναμονή για την ολοκλήρωση της εκτέλεσης των νημάτων

Εάν θέλετε το thread1 να ολοκληρώσει την εκτέλεση, μπορείτε να καλέσετε τη μέθοδο join() σε αυτήν μετά την έναρξη του νήματος. Με αυτόν τον τρόπο θα περιμένει να ολοκληρωθεί η εκτέλεση του νήματος1 χωρίς να μεταβείτε στο κύριο νήμα.

import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
thread1.join()
print(threading.active_count())

Τώρα, το νήμα1 έχει ολοκληρώσει την εκτέλεση προτού εκτυπώσουμε τον αριθμό ενεργών νημάτων. Επομένως, εκτελείται μόνο το κύριο νήμα, πράγμα που σημαίνει ότι ο αριθμός ενεργών νημάτων είναι ένα. ✅

# Output
Running some_func...
Finished running some_func.
1

Πώς να εκτελέσετε πολλά νήματα στην Python

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

Εδώ, η count_down είναι μια συνάρτηση που παίρνει έναν αριθμό ως όρισμα και μετρά αντίστροφα από αυτόν τον αριθμό στο μηδέν.

def count_down(n):
    for i in range(n,-1,-1):
        print(i)

Ορίζουμε count_up, μια άλλη συνάρτηση Python που μετράει από το μηδέν μέχρι έναν δεδομένο αριθμό.

def count_up(n):
    for i in range(n+1):
        print(i)

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

  Ένας οριστικός οδηγός για την ανάλυση συναισθημάτων

– Για να μετρήσετε αντίστροφα από έναν συγκεκριμένο αριθμό στο μηδέν, μπορείτε να χρησιμοποιήσετε μια αρνητική τιμή βήματος -1 και να ορίσετε την τιμή διακοπής σε -1, ώστε να συμπεριληφθεί το μηδέν.

– Ομοίως, για να μετρήσετε μέχρι το n, πρέπει να ορίσετε την τιμή διακοπής σε n + 1. Επειδή οι προεπιλεγμένες τιμές έναρξης και βήματος είναι 0 και 1, αντίστοιχα, μπορείτε να χρησιμοποιήσετε το εύρος (n + 1) για να λάβετε την ακολουθία 0 μέσω ν.

Στη συνέχεια, ορίζουμε δύο νήματα, το νήμα1 και το νήμα2 για την εκτέλεση των συναρτήσεων count_down και count_up, αντίστοιχα. Προσθέτουμε δηλώσεις εκτύπωσης και λειτουργίες ύπνου και για τις δύο λειτουργίες.

Κατά τη δημιουργία των αντικειμένων νήματος, παρατηρήστε ότι τα ορίσματα στη συνάρτηση προορισμού πρέπει να καθοριστούν ως πλειάδα—στην παράμετρο args. Καθώς και οι δύο συναρτήσεις (count_down και count_up) λαμβάνουν ένα όρισμα. Θα πρέπει να εισαγάγετε ένα κόμμα ρητά μετά την τιμή. Αυτό διασφαλίζει ότι το όρισμα εξακολουθεί να μεταβιβάζεται ως πλειάδα, καθώς τα επόμενα στοιχεία συνάγονται ως Κανένα.

import threading
import time

def count_down(n):
    for i in range(n,-1,-1):
        print("Running thread1....")
        print(i)
        time.sleep(1)


def count_up(n):
    for i in range(n+1):
        print("Running thread2...")
        print(i)
        time.sleep(1)

thread1 = threading.Thread(target=count_down,args=(10,))
thread2 = threading.Thread(target=count_up,args=(5,))
thread1.start()
thread2.start()

Στην έξοδο:

  • Η συνάρτηση count_up εκτελείται στο νήμα2 και μετράει μέχρι το 5 ξεκινώντας από το 0.
  • Η συνάρτηση count_down εκτελείται σε νήμα1 αντίστροφες μετρήσεις από 10 έως 0.
# Output
Running thread1....
10
Running thread2...
0
Running thread1....
9
Running thread2...
1
Running thread1....
8
Running thread2...
2
Running thread1....
7
Running thread2...
3
Running thread1....
6
Running thread2...
4
Running thread1....
5
Running thread2...
5
Running thread1....
4
Running thread1....
3
Running thread1....
2
Running thread1....
1
Running thread1....
0

Μπορείτε να δείτε ότι το νήμα1 και το νήμα2 εκτελούνται εναλλακτικά, καθώς και τα δύο περιλαμβάνουν μια λειτουργία αναμονής (αναμονή). Μόλις η συνάρτηση count_up ολοκληρώσει τη μέτρηση μέχρι το 5, το νήμα2 δεν είναι πλέον ενεργό. Έτσι παίρνουμε την έξοδο που αντιστοιχεί μόνο στο νήμα1.

Ανακεφαλαίωση

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

  • Ο κατασκευαστής Thread μπορεί να χρησιμοποιηθεί για τη δημιουργία ενός αντικειμένου νήματος. Χρησιμοποιώντας το threading.Thread(target=,args=(<πλήθος args>)) δημιουργείται ένα νήμα που εκτελεί τον καλούμενο στόχο με ορίσματα που καθορίζονται στα args.
  • Το πρόγραμμα Python εκτελείται σε ένα κύριο νήμα, επομένως τα αντικείμενα νήματος που δημιουργείτε είναι πρόσθετα νήματα. Μπορείτε να καλέσετε τη συνάρτηση active_count() που επιστρέφει τον αριθμό των ενεργών νημάτων σε οποιαδήποτε περίπτωση.
  • Μπορείτε να ξεκινήσετε ένα νήμα χρησιμοποιώντας τη μέθοδο start() στο αντικείμενο νήμα και να περιμένετε μέχρι να ολοκληρωθεί η εκτέλεσή του χρησιμοποιώντας τη μέθοδο join().

Μπορείτε να κωδικοποιήσετε πρόσθετα παραδείγματα τροποποιώντας τους χρόνους αναμονής, επιχειρώντας μια διαφορετική λειτουργία I/O και πολλά άλλα. Φροντίστε να εφαρμόσετε το multithreading στα επερχόμενα έργα Python σας. Καλή κωδικοποίηση!🎉