Ответ
Многопроцессорность в Python используется для выполнения CPU-bound задач, так как она позволяет обойти ограничение Global Interpreter Lock (GIL), запуская код в отдельных процессах с собственным интерпретатором и памятью. Основным инструментом является модуль multiprocessing.
Ключевые компоненты multiprocessing:
1. Класс Process
Создает и управляет отдельным процессом. Аналогичен threading.Thread.
from multiprocessing import Process
import os
def info(title):
print(title)
print('parent process:', os.getppid())
print('process id:', os.getpid())
if __name__ == '__main__':
p = Process(target=info, args=('Функция info',))
p.start()
p.join()
2. Класс Pool
Управляет пулом рабочих процессов, что удобно для распараллеливания выполнения функции на наборе данных (задачи в стиле map-reduce).
from multiprocessing import Pool
def square(x):
return x * x
if __name__ == '__main__':
# Создаем пул из 4 процессов
with Pool(processes=4) as pool:
# Распределяем вычисления по процессам
results = pool.map(square, range(10))
print(results)
3. Средства межпроцессного взаимодействия (IPC) Поскольку процессы не разделяют память, для обмена данными нужны специальные механизмы:
Queue: Процессно-безопасная очередь (FIFO).Pipe: Двусторонний канал связи между двумя процессами.
Пример с Queue:
from multiprocessing import Process, Queue
def worker(q):
q.put([42, None, 'hello'])
if __name__ == '__main__':
q = Queue()
p = Process(target=worker, args=(q,))
p.start()
print(q.get()) # Блокируется до получения данных
p.join()
4. Разделяемая память Для обмена простыми данными без сериализации можно использовать:
Value: Разделяемый объект для одного значения (например,c_int,c_double).Array: Разделяемый массив фиксированного размера и типа.
Ответ 18+ 🔞
А, ну вот, опять про эти ваши процессы в питоне, блядь. Слушай, смотри, сейчас объясню, как это работает, чтобы ты не тупил, как Герасим из «Муму», который только «ууу» мог сказать, блядь.
Короче, есть у питона такая штука — GIL, глобальный интерпретаторный замок, ёпта. Он такой злобный сторож, который не даёт нескольким потокам одновременно работать с процессором, если задача про вычисления, а не про ожидание. Ну, типа, чтобы не устроили бардак в памяти. И что делать, если тебе надо, чтобы несколько ядер твоего процессора пахали, а не одно? Правильно, блядь, запускать отдельные процессы! У каждого свой интерпретатор, своя память, и GIL им похуй — они друг другу не мешают. Как будто несколько Герасимов, каждый в своём огороде картошку копает, а не один за всех них хуярит.
Вот для этого есть модуль multiprocessing. Главные его герои, блядь:
1. Класс Process
Прямой аналог потока, только процесс. Создаёшь — запускаешь — ждёшь.
from multiprocessing import Process
import os
def info(title):
print(title)
print('parent process:', os.getppid())
print('process id:', os.getpid())
if __name__ == '__main__':
p = Process(target=info, args=('Функция info',))
p.start()
p.join()
Смотри, тут важно: if __name__ == '__main__': — это не прихоть, а защита от рекурсивного создания процессов, когда скрипт импортируется. Без этого может быть пиздец, процессы начнут плодиться, как тараканы.
2. Класс Pool
Это уже, блядь, целая бригада Герасимов! Удобно, когда тебе надо кучу однотипной работы раздать. Типа «всем взять по числу и возвести в квадрат».
from multiprocessing import Pool
def square(x):
return x * x
if __name__ == '__main__':
# Создаем пул из 4 процессов
with Pool(processes=4) as pool:
# Распределяем вычисления по процессам
results = pool.map(square, range(10))
print(results)
Вот тут pool.map — это магия. Он сам разбросает задачи по процессам и соберёт результаты. Красота, а не жизнь.
3. А как они общаются, эти процессы? А нихуя не общаются, потому что память у каждого своя! Это не потоки, где всё общее. Тут надо специальные шлюзы строить.
Queue: Очередь, процессобезопасная. Один процесс туда кладёт, другой оттуда забирает. Как почтовый ящик, блядь.Pipe: Труба между двумя процессами. Прямая связь, типа рации.
Вот смотри, как с очередью:
from multiprocessing import Process, Queue
def worker(q):
q.put([42, None, 'hello'])
if __name__ == '__main__':
q = Queue()
p = Process(target=worker, args=(q,))
p.start()
print(q.get()) # Блокируется до получения данных
p.join()
Рабочий процесс швыряет в очередь список, а главный процесс его выуживает. Всё просто, как три копейки.
4. А если очень хочется общую память?
Ну, для особо упоротых есть Value и Array. Это такие кусочки памяти, к которым могут обращаться несколько процессов. Но, блядь, осторожно! Тут уже надо думать про блокировки, чтобы они друг другу данные не испортили, как те Герасимы, если бы начали одну грядку копать.
Вот и вся история, ёпта. Многопроцессорность — это когда тебе надо загрузить все ядра процессора по полной, а не один поток гонять. Главное — помни, что процессы тяжеловесные, создавать их — не быстро, и общаться им сложнее. Но для CPU-bound задач — это то, что доктор прописал. А для I/O-bound (когда много ждёшь) — смотри в сторону асинхронности или потоков.
Понял? Иди, пробуй. Только не утопи свою «Муму» в процессе отладки, а то совесть потом заест, как тому Герасиму, блядь.