Πώς να χρησιμοποιήσετε το threading στην Python;

Προσπαθώ να κατανοήσω την επεξεργασία νημάτων στην Python. Έχω κοιτάξει την τεκμηρίωση και τα παραδείγματα, αλλά ειλικρινά, πολλά παραδείγματα είναι υπερβολικά περίπλοκα και δυσκολεύομαι να τα καταλάβω.

Πώς μπορείτε να δείξετε με σαφήνεια ότι οι εργασίες χωρίζονται για πολυνηματικότητα;

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

import Queue
import threading
import urllib2

# called by each thread
def get_url(q, url):
    q.put(urllib2.urlopen(url).read())

theurls = ["http://google.com", "http://yahoo.com"]

q = Queue.Queue()

for u in theurls:
    t = threading.Thread(target=get_url, args = (q,u))
    t.daemon = True
    t.start()

s = q.get()
print s

Αυτή είναι μια περίπτωση όπου το threading χρησιμοποιείται ως απλή βελτιστοποίηση: κάθε υπονήμα περιμένει να επιλύσει και να απαντήσει ένα URL, προκειμένου να τοποθετήσει τα περιεχόμενά του στην ουρά- κάθε νήμα είναι ένα δαίμονας (δεν θα'κρατήσει τη διαδικασία αν το κύριο νήμα τερματιστεί -- αυτό'είναι πιο συνηθισμένο από ότι όχι)- το κύριο νήμα ξεκινάει όλα τα υπονήματα, κάνει ένα get στην ουρά για να περιμένει μέχρι ένα από αυτά να κάνει ένα put, στη συνέχεια εκπέμπει τα αποτελέσματα και τερματίζει (το οποίο κατεβάζει κάθε υπονήμα που μπορεί να τρέχει ακόμα, αφού είναι νήματα δαίμονας).

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

Σχόλια (14)

ΣΗΜΕΙΩΣΗ: Για πραγματικό παραλληλισμό στην Python, θα πρέπει να χρησιμοποιήσετε την ενότητα multiprocessing για να διακλαδώσετε πολλαπλές διεργασίες που εκτελούνται παράλληλα (λόγω του παγκόσμιου κλειδώματος του διερμηνέα, τα νήματα της Python παρέχουν διαχωρισμό αλλά στην πραγματικότητα εκτελούνται σειριακά, όχι παράλληλα, και είναι χρήσιμα μόνο όταν διαχωρίζονται οι λειτουργίες εισόδου/εξόδου).

Ωστόσο, αν ψάχνετε απλώς για διαπλοκή (ή εκτελείτε λειτουργίες εισόδου/εξόδου που μπορούν να παραλληλιστούν παρά το παγκόσμιο κλείδωμα του διερμηνευτή), τότε η ενότητα threading είναι το κατάλληλο μέρος για να ξεκινήσετε. Ως ένα πραγματικά απλό παράδειγμα, ας'ρήσουμε το πρόβλημα της άθροισης μιας μεγάλης περιοχής με παράλληλη άθροιση υποπεριοχών:

import threading

class SummingThread(threading.Thread):
     def __init__(self,low,high):
         super(SummingThread, self).__init__()
         self.low=low
         self.high=high
         self.total=0

     def run(self):
         for i in range(self.low,self.high):
             self.total+=i

thread1 = SummingThread(0,500000)
thread2 = SummingThread(500000,1000000)
thread1.start() # This actually causes the thread to run
thread2.start()
thread1.join()  # This waits until the thread has completed
thread2.join()  
# At this point, both threads have completed
result = thread1.total + thread2.total
print result

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

Σχόλια (8)

Όπως ανέφεραν και άλλοι, η CPython μπορεί να χρησιμοποιήσει νήματα μόνο για αναμονές I\O λόγω του GIL. Αν θέλετε να επωφεληθείτε από τους πολλαπλούς πυρήνες για εργασίες που είναι συνδεδεμένες με τη CPU, χρησιμοποιήστε multiprocessing:

from multiprocessing import Process

def f(name):
    print 'hello', name

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()
Σχόλια (5)