Python Language
Процессы и потоки
Поиск…
Вступление
Большинство программ выполняются по строкам, за один раз выполняется только один процесс. Нитки позволяют нескольким процессам течь независимо друг от друга. Threading с несколькими процессорами позволяет программам запускать несколько процессов одновременно. В этом разделе описывается реализация и использование потоков в Python.
Глобальная блокировка переводчика
Частота многопоточности Python часто страдает из-за блокировки Global Interpreter . Короче говоря, хотя вы можете иметь несколько потоков в программе Python, только одна команда байт-кода может выполняться параллельно в любой момент времени, независимо от количества процессоров.
Таким образом, многопоточность в случаях, когда операции блокируются внешними событиями, такими как доступ к сети, может быть весьма эффективной:
import threading
import time
def process():
time.sleep(2)
start = time.time()
process()
print("One run took %.2fs" % (time.time() - start))
start = time.time()
threads = [threading.Thread(target=process) for _ in range(4)]
for t in threads:
t.start()
for t in threads:
t.join()
print("Four runs took %.2fs" % (time.time() - start))
# Out: One run took 2.00s
# Out: Four runs took 2.00s
Обратите внимание: несмотря на то, что для каждого process
потребовалось 2 секунды для выполнения, четыре процесса вместе могли эффективно работать параллельно, занимая 2 секунды.
Однако многопоточность в случаях, когда интенсивные вычисления выполняются в коде Python - например, при большом количестве вычислений - не приводит к значительному улучшению и даже может быть медленнее, чем параллельная работа:
import threading
import time
def somefunc(i):
return i * i
def otherfunc(m, i):
return m + i
def process():
for j in range(100):
result = 0
for i in range(100000):
result = otherfunc(result, somefunc(i))
start = time.time()
process()
print("One run took %.2fs" % (time.time() - start))
start = time.time()
threads = [threading.Thread(target=process) for _ in range(4)]
for t in threads:
t.start()
for t in threads:
t.join()
print("Four runs took %.2fs" % (time.time() - start))
# Out: One run took 2.05s
# Out: Four runs took 14.42s
В последнем случае многопроцессорность может быть эффективной, поскольку несколько процессов могут, разумеется, выполнять несколько команд одновременно:
import multiprocessing
import time
def somefunc(i):
return i * i
def otherfunc(m, i):
return m + i
def process():
for j in range(100):
result = 0
for i in range(100000):
result = otherfunc(result, somefunc(i))
start = time.time()
process()
print("One run took %.2fs" % (time.time() - start))
start = time.time()
processes = [multiprocessing.Process(target=process) for _ in range(4)]
for p in processes:
p.start()
for p in processes:
p.join()
print("Four runs took %.2fs" % (time.time() - start))
# Out: One run took 2.07s
# Out: Four runs took 2.30s
Работа в нескольких потоках
Используйте threading.Thread
для запуска функции в другом потоке.
import threading
import os
def process():
print("Pid is %s, thread id is %s" % (os.getpid(), threading.current_thread().name))
threads = [threading.Thread(target=process) for _ in range(4)]
for t in threads:
t.start()
for t in threads:
t.join()
# Out: Pid is 11240, thread id is Thread-1
# Out: Pid is 11240, thread id is Thread-2
# Out: Pid is 11240, thread id is Thread-3
# Out: Pid is 11240, thread id is Thread-4
Работа в нескольких процессах
Используйте multiprocessing.Process
для запуска функции в другом процессе. Интерфейс похож на threading.Thread
:
import multiprocessing
import os
def process():
print("Pid is %s" % (os.getpid(),))
processes = [multiprocessing.Process(target=process) for _ in range(4)]
for p in processes:
p.start()
for p in processes:
p.join()
# Out: Pid is 11206
# Out: Pid is 11207
# Out: Pid is 11208
# Out: Pid is 11209
Совместное использование между потоками
Поскольку все потоки работают в одном и том же процессе, все потоки имеют доступ к тем же данным.
Однако одновременный доступ к общим данным должен быть защищен блокировкой во избежание проблем синхронизации.
import threading
obj = {}
obj_lock = threading.Lock()
def objify(key, val):
print("Obj has %d values" % len(obj))
with obj_lock:
obj[key] = val
print("Obj now has %d values" % len(obj))
ts = [threading.Thread(target=objify, args=(str(n), n)) for n in range(4)]
for t in ts:
t.start()
for t in ts:
t.join()
print("Obj final result:")
import pprint; pprint.pprint(obj)
# Out: Obj has 0 values
# Out: Obj has 0 values
# Out: Obj now has 1 values
# Out: Obj now has 2 valuesObj has 2 values
# Out: Obj now has 3 values
# Out:
# Out: Obj has 3 values
# Out: Obj now has 4 values
# Out: Obj final result:
# Out: {'0': 0, '1': 1, '2': 2, '3': 3}
Совместное использование состояний между процессами
Код, выполняющийся в разных процессах, по умолчанию не использует одни и те же данные. Тем не менее, модуль multiprocessing
содержит примитивы, которые помогают совместно использовать значения для нескольких процессов.
import multiprocessing
plain_num = 0
shared_num = multiprocessing.Value('d', 0)
lock = multiprocessing.Lock()
def increment():
global plain_num
with lock:
# ordinary variable modifications are not visible across processes
plain_num += 1
# multiprocessing.Value modifications are
shared_num.value += 1
ps = [multiprocessing.Process(target=increment) for n in range(4)]
for p in ps:
p.start()
for p in ps:
p.join()
print("plain_num is %d, shared_num is %d" % (plain_num, shared_num.value))
# Out: plain_num is 0, shared_num is 4