Класически проблем е да накараме една програма да върши няколко неща едновременно. Има два стандартни подхода в повечето езици - 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