Что такое процесс в Python?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое процесс в Python
Процесс в Python (как и в любой операционной системе) — это отдельный экземпляр программы, работающий с собственным адресным пространством памяти, независимым от других процессов. Это фундаментальное понятие в многопроцессной обработке и параллелизме.
Основные характеристики процесса
1. Независимое адресное пространство памяти
Каждый процесс имеет собственную память, изолированную от других процессов:
import os
from multiprocessing import Process
global_var = 100 # Глобальная переменная в главном процессе
def change_variable():
global global_var
global_var = 200
print(f"В процессе: global_var = {global_var}, PID = {os.getpid()}")
if __name__ == "__main__":
print(f"Главный процесс: global_var = {global_var}, PID = {os.getpid()}")
p = Process(target=change_variable)
p.start()
p.join()
# Результат:
# В процессе: global_var = 200, PID = 12345
# Главный процесс: global_var = 100, PID = 98765
# Изменение в дочернем процессе не повлияло на главный процесс!
print(f"Главный процесс после: global_var = {global_var}, PID = {os.getpid()}")
2. PID (Process ID) — уникальный идентификатор
Каждому процессу операционная система выделяет уникальный номер:
import os
from multiprocessing import Process
def show_pid():
print(f"Мой PID: {os.getpid()}")
print(f"PID моего родителя: {os.getppid()}")
if __name__ == "__main__":
print(f"Главный процесс PID: {os.getpid()}")
for i in range(3):
p = Process(target=show_pid, name=f"Worker-{i}")
p.start()
p.join()
3. Независимый GIL (Global Interpreter Lock)
Самое важное: каждый процесс имеет собственный GIL! Это означает, что процессы могут выполняться на разных ядрах процессора параллельно:
import time
from multiprocessing import Process
def cpu_heavy_task(n):
"""Процессор-интенсивная задача"""
count = 0
for i in range(n):
count += i
return count
if __name__ == "__main__":
# Тестируем на 4 ядрах
n = 100_000_000
# Однопроцессный вариант (с GIL)
start = time.time()
cpu_heavy_task(n)
cpu_heavy_task(n)
cpu_heavy_task(n)
cpu_heavy_task(n)
single_time = time.time() - start
print(f"Однопроцессно: {single_time:.2f} сек")
# Многопроцессный вариант (без GIL между процессами)
start = time.time()
processes = []
for _ in range(4):
p = Process(target=cpu_heavy_task, args=(n,))
p.start()
processes.append(p)
for p in processes:
p.join()
multi_time = time.time() - start
print(f"Многопроцессно: {multi_time:.2f} сек")
print(f"Ускорение: {single_time / multi_time:.2f}x")
# Результат: примерно 3-4x ускорение на 4 ядрах!
Процесс vs Поток (Thread) vs Корутина (Coroutine)
| Характеристика | Процесс | Поток | Корутина |
|---|---|---|---|
| Память | Отдельная для каждого | Общая в процессе | Общая в процессе |
| GIL | Свой для каждого | Общий (блокирует друг друга) | Нет GIL, но один за раз |
| Создание | Медленно (100+ ms) | Быстро (1 ms) | Очень быстро (микросекунды) |
| Переключение контекста | Дорого (ОС контролирует) | Дорого (ОС контролирует) | Дешево (программа контролирует) |
| Синхронизация | Сложная (IPC) | Проще (shared memory) | Не нужна |
| Параллелизм | Истинный параллелизм | Псевдопараллелизм (GIL) | Квазипараллелизм |
Создание процессов в Python
1. multiprocessing.Process
from multiprocessing import Process
import time
def worker(name, duration):
print(f"{name} начал работу")
time.sleep(duration)
print(f"{name} завершил работу")
if __name__ == "__main__":
# Создание процессов
p1 = Process(target=worker, args=("Worker-1", 2))
p2 = Process(target=worker, args=("Worker-2", 3))
# Запуск
p1.start()
p2.start()
# Ожидание завершения
p1.join()
p2.join()
print("Все процессы завершены")
2. ProcessPoolExecutor для пула процессов
from concurrent.futures import ProcessPoolExecutor
import time
def calculate_square(n):
time.sleep(1) # Имитация работы
return n * n
if __name__ == "__main__":
numbers = [1, 2, 3, 4, 5]
# Пул из 4 процессов
with ProcessPoolExecutor(max_workers=4) as executor:
results = list(executor.map(calculate_square, numbers))
print(results) # [1, 4, 9, 16, 25]
# Время выполнения: ~1 сек (параллельно), вместо 5 сек (последовательно)
Обмен данными между процессами (IPC — Inter-Process Communication)
1. Queue — очередь
from multiprocessing import Process, Queue
def producer(q):
for i in range(5):
q.put(i)
q.put(None) # Сигнал конца
def consumer(q):
while True:
item = q.get()
if item is None:
break
print(f"Получено: {item}")
if __name__ == "__main__":
q = Queue()
p1 = Process(target=producer, args=(q,))
p2 = Process(target=consumer, args=(q,))
p1.start()
p2.start()
p1.join()
p2.join()
2. Pipe — двусторонний канал
from multiprocessing import Process, Pipe
def child_process(conn):
conn.send("Привет из дочернего процесса")
print(f"Дочерний получил: {conn.recv()}")
conn.close()
if __name__ == "__main__":
parent_conn, child_conn = Pipe()
p = Process(target=child_process, args=(child_conn,))
p.start()
print(f"Родитель получил: {parent_conn.recv()}")
parent_conn.send("Привет из родительского процесса")
p.join()
3. Shared Memory — общая память
from multiprocessing import Process, Value, Array
def increment_value(shared_val):
with shared_val.get_lock():
shared_val.value += 1
def modify_array(shared_arr):
for i in range(len(shared_arr)):
shared_arr[i] *= 2
if __name__ == "__main__":
# Общее целое число
shared_int = Value('i', 0) # 'i' = integer type
# Общий массив
shared_array = Array('i', [1, 2, 3, 4, 5])
processes = []
for _ in range(5):
p = Process(target=increment_value, args=(shared_int,))
processes.append(p)
p.start()
p2 = Process(target=modify_array, args=(shared_array,))
p2.start()
processes.append(p2)
for p in processes:
p.join()
print(f"Итоговое значение: {shared_int.value}") # 5
print(f"Итоговый массив: {list(shared_array)}") # [2, 4, 6, 8, 10]
Состояния процесса
from multiprocessing import Process
import time
def long_task():
time.sleep(5)
if __name__ == "__main__":
p = Process(target=long_task)
print(f"Перед стартом: is_alive() = {p.is_alive()}") # False
p.start()
print(f"После старта: is_alive() = {p.is_alive()}") # True
time.sleep(1)
print(f"Процесс активен: {p.is_alive()}") # True
p.join()
print(f"После join: is_alive() = {p.is_alive()}") # False
Когда использовать процессы
# 1. CPU-bound задачи (обработка данных, вычисления)
from concurrent.futures import ProcessPoolExecutor
def heavy_computation(data):
return sum(data)
if __name__ == "__main__":
large_datasets = [[i]*1000000 for i in range(1, 5)]
with ProcessPoolExecutor(max_workers=4) as executor:
results = executor.map(heavy_computation, large_datasets)
# 2. Изоляция для безопасности
from multiprocessing import Process
def run_untrusted_code():
# Даже если код сломается, главный процесс выживет
eval(user_input)
if __name__ == "__main__":
p = Process(target=run_untrusted_code)
p.start()
p.join()
# 3. Долгоживущие фоновые задачи
def background_worker():
while True:
# Бесконечный рабочий цикл
pass
if __name__ == "__main__":
p = Process(target=background_worker, daemon=True)
p.start()
Важные замечания
# ОШИБКА: забыли if __name__ == "__main__"
from multiprocessing import Process
def worker():
print("Работаю")
Process(target=worker).start() # На Windows зависнет!
# ПРАВИЛЬНО:
if __name__ == "__main__":
Process(target=worker).start()
# Это нужно потому что на Windows используется spawn, а на Unix fork
# На Windows нужно переимпортировать модуль, поэтому код должен быть в защищённом блоке
Заключение
Процесс в Python — это мощный инструмент для истинного параллелизма при работе с CPU-intensive задачами. Каждый процесс имеет собственное адресное пространство и GIL, что позволяет выполняться на разных ядрах процессора одновременно. Однако создание процессов дороже, чем потоков, так что используй их для тяжёлых вычислительных задач.