Класически проблем е да накараме една програма да върши няколко неща едновременно. Има два стандартни подхода в повечето езици - fork-ове и нишки. Както можете да очаквате, те се срещат в Python в модулите os
и threading
.
fork
е функция, чието изпълнение създава ново копие на програмата, което продължава от същото място където е извикана функцията. Досега заделените ресурси са общи за двата процеса, докато новите са локални. Създава се нов процес на ниво операционна система. В родителския процес функцията връща id-то на дъщерния, докато във дъщерния връща 0.
import os
print "pre-forking"
pid = os.fork()
print "post-forking", pid
pre-forking post-forking 0 post-forking 6450
Отделни процеси могат да си комуникират по-успешно с други функции в модула os
. Като цяло fork
е твърде примитивен подход когато имате нужда от конкурентност в приложението и в почти всички случаи е много по-добре да използвате по-високо ниво на абстракция - нишки.
Python предлага два модула за работа с нишки - thread
и threading
. Втория предлага по-високо ниво на абстракция и по-удобен интерфейс. Съответно е по-логичния избор от двата модула, ако нямате смислена причина да предпочетете thread
.
Класът Thread
ви дава прост механизъм да изпълнявате код в нова нишка. Нужно е да предоставите метод run
, който да съдържа кода на нишката. След това я инстанцирате и викате методът и start
.
import threading
class MyThread(threading.Thread):
def run(self):
for i in range(0, 10000): print i
thread = MyThread()
thread.start()
for i in range(0, 10000): print -i
Стандартен проблем при конкуренти задачи са "критични секции" от програмата - такива, които трябва да бъдат изпълнявани само веднъж в даден момент. Това може да се постигне с класът Lock
. Неговите инстанции имат два метода - acquire()
и block()
. Класът вътре пази флаг дали lock-а е свободен или зает. Ако acquire()
се извика на свободен lock, флага се вдига и lock-а става зает. Ако acquire()
се извика на зает lock, метода блокира докато lock-а не се освободи. Извикването на release()
освобождава lock-а.
import threading
lock = threading.Lock()
class Gatherer(threading.Thread):
def run(self):
while True:
foundStuff = gather()
lock.acquire()
for thing in foundStuff: report(thing)
lock.release()
g1, g2 = Gatherer(), Gatherer()
g1.start()
g2.start()
Можете да използвате with с Lock.
from __future__ import with_statement
import threading
lock = threading.Lock()
with lock:
print "Do stuff"
Semaphore
представлява механизъм за разрешаване на конкурентен достъп,
много подобен на Lock
разликата е там, че вместо флаг, вътрешно се пази
брояч. acquire()
го намалява, докато release()
го увеличава.
acquire()
се разблокира, когато брояча е по-голям от нула и блокира, ако
той е нула.
Event
е проста комуникация между нишки, в която няколко нишки изчакват
една да завърши с някакъв резултат. Нишките които изчакват викат метода wait()
и изпълнението им блокира. Нишката, която продуцира резултата извиква метода
set()
. В този момент всички нишки които чакат продължават изпълнението си.
Condition
е много сходен с Lock
, но дава няколко нови метода.
wait()
освобождава lock-а, след което блокира докато не се събуди с извикване
на notify()
или notifyAll()
. Като се "събуди", отново взема
lock-а. notify()
събужда една нишка, заспала с wait()
.
notifyAll()
събуджа всички.
# Consume one item
cv.acquire()
while not an_item_is_available():
cv.wait()
get_an_available_item()
cv.release()
# Produce one item
cv.acquire()
make_an_item_available()
cv.notify()
cv.release()
Инстанциите на local
представляват обекти, които са
"локални" за дадена нишка. Тяхните атрибути са уникални за всяка нишка.
Например:
import threading
foo = threading.local()
foo.blah = 1
class MyThread(threading.Thread):
def run(self):
if (hasattr(foo, 'blah')): print "MyThread sees foo.blah"
foo.blah = 4
print "MyThread(foo.blah) = ", foo.blah
MyThread().start()
print "BaseThread(foo.blah) = ", foo.blah