Kako uporabljati niti v Pythonu?

Poskušam razumeti nitkanje v Pythonu. Ogledal sem si dokumentacijo in primere, vendar so, odkrito povedano, mnogi primeri prezahtevni in jih težko razumem.

Kako jasno prikažete razdelitev opravil za večnitnost?

Tukaj je preprost primer: preizkusiti morate nekaj alternativnih naslovov URL in vrniti vsebino prvega, ki se odzove.

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

To je primer, v katerem se nitkanje uporablja kot preprosta optimizacija: Vsaka podnita čaka, da se URL razreši in odgovori, da bi njegovo vsebino prenesla v čakalno vrsto; vsaka nit je demon (ne bo vzdrževala procesa, če se glavna nit konča - to je bolj pogosto kot ne); glavna nit zažene vse podniti, izvede get na čakalno vrsto in počaka, da ena od njih izvede put, nato pošlje rezultate in se konča (s tem se končajo vse podniti, ki morda še tečejo, saj so niti demoni).

Pravilna uporaba niti v Pythonu je vedno povezana z vhodno-izhodnimi operacijami (ker CPython tako ali tako ne uporablja več jeder za izvajanje nalog, vezanih na procesor, je edini razlog za uporabo niti ta, da ne blokira procesa, medtem ko se čaka na kakšen vhodno-izhodni postopek). Čakalne vrste so skoraj vedno najboljši način za porazdelitev dela na niti in/ali zbiranje rezultatov dela, poleg tega pa so po svoji naravi varne za niti, tako da vam prihranijo skrbi z zaklepanjem, pogoji, dogodki, semaforji in drugimi koncepti usklajevanja/komuniciranja med nitmi.

Komentarji (14)

OPOMBA: Za dejansko paralelizacijo v Pythonu morate uporabiti modul multiprocessing za razdvajanje več procesov, ki se izvajajo vzporedno (zaradi globalne blokade tolmača niti v Pythonu omogočajo prepletanje, vendar se dejansko izvajajo zaporedno in ne vzporedno, zato so uporabne le pri prepletanju I/O operacij).

Če pa iščete le prepletanje (ali izvajate operacije I/O, ki jih je kljub globalni blokadi tolmača mogoče vzporediti), potem je modul threading pravi začetek. Kot zelo preprost primer si oglejmo problem seštevanja velikega razpona z vzporednim seštevanjem podrazponov:

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

Upoštevajte, da je zgornji primer zelo neumen, saj ne izvaja popolnoma nobenega vnosa/iznosa in se bo v CPythonu zaradi globalne blokade prevajalnika izvajal zaporedno, čeprav izmenično (z dodatnimi režijskimi stroški preklapljanja konteksta).

Komentarji (8)

Kot so omenili drugi, lahko CPython zaradi GIL uporablja niti samo za čakanje na I\O. Če želite izkoristiti več jeder za naloge, vezane na procesor, uporabite multiprocessing:

from multiprocessing import Process

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

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()
Komentarji (5)