Компьютеры, которые мы используем сегодня, уже вступили в эру многопроцессорных или многоядерных процессоров, и все операционные системы, которые мы используем, являются операционными системами, которые поддерживают «многозадачность», что позволяет нам запускать несколько программ одновременно или разлагать задачи на несколько относительных независимых подзадач, и позволяет выполнять несколько подзадач одновременно, тем самым сокращая время выполнения программы, а также позволяя пользователям получить лучший опыт. Таким образом, независимо от того, какой язык программирования используется для разработки в настоящий момент нам нужно сначала обсудить две концепции: одна называется процессом, а другая - потоком.
Процесс - это программа, выполняемая в операционной системе. Операционная система выделяет пространство для хранения в единицах процессов. Каждый процесс имеет собственное адресное пространство, стек данных и другие вспомогательные данные, используемые для отслеживания выполнения процесса. Операционная система управляет выполнением всех процессов, разумно распределяя ресурсы под них. Процессы могут использовать fork или spawn для создания новых процессов для выполнения других задач, но новые процессы также имеют свое собственное независимое пространство памяти, поэтому совместное использование данных должно осуществляться с помощью механизма межпроцессного взаимодействия (IPC, Inter-Process Communication). Конкретные методы включают каналы, сигналы, сокеты, области разделяемой памяти и так далее.
Процесс также может иметь несколько параллельных потоков выполнения. Проще говоря, у него есть несколько исполнительных модулей, которые могут быть запланированы ЦП - это так называемый поток. Поскольку потоки находятся в одном процессе, они могут использовать один и тот же контекст, поэтому по сравнению с процессами обмен информацией и обмен данными между потоками проще. Конечно, в одноядерной системе ЦП настоящий параллелизм невозможен, потому что в определенный момент есть только один поток, который может получить ЦП, а несколько потоков разделяют время выполнения ЦП. Само собой разумеется, что использование многопоточности для достижения параллельного программирования приносит пользу программе. Наиболее важным проявлением является повышение производительности программы и улучшение взаимодействия с пользователем. Практически все программное обеспечение, которое мы используем сегодня, использует технологию многопоточности. Система поставляется с инструментом мониторинга процессов например, «Монитор активности» в macOS или «Диспетчер задач» в Windows.
Конечно, многопоточность не лишена своих недостатков. С точки зрения других процессов, многопоточные программы тормозят выполненние кода для других программ, потому что ресурс Вашего компьютера не безграничен, и выполнение многопоточных программ нагружает ЦП больше, нежели другие программы. С точки зрения разработчиков, написание и отладка многопоточных программ предъявляют более высокие требования к разработчикам и сложнее для новичков.
Python поддерживает как многопроцессорность, так и многопоточность, поэтому существует три основных способа использования Python для реализации параллельного программирования: многопроцессорность, многопоточность, многопроцессорность + многопоточность.
Процесс в операционной системе - это программа или приложение, которое находится в состоянии выполнения на центральном процессоре компьютера. Процессы могут работать независимо друг от друга и иметь свои собственные ресурсы, такие как память и файлы.
Пример процесса в Python может выглядеть так:
import os
# Создание нового процесса
pid = os.fork()
if pid == 0:
# Этот код будет выполняться в дочернем процессе
print("Дочерний процесс: ", os.getpid())
else:
# Этот код будет выполняться в родительском процессе
print("Родительский процесс: ", os.getpid())
# Общий код для дочернего и родительского процессов
print("Конец работы.")В этом примере мы используем модуль os для создания нового процесса. Метод fork() создает точную копию текущего
процесса, которая продолжает выполняться отдельно от родительского процесса. В зависимости от результата fork() мы
можем определить, где мы находимся - в дочернем или родительском процессе. В конце общий код выводит сообщение о
завершении работы.
Поток - это меньший блок выполнения внутри процесса, который использует общее пространство памяти и ресурсы с другими потоками процесса. Многопоточные приложения используют несколько потоков для выполнения разных задач параллельно.
Пример сценария многопоточного приложения на Python:
import threading
import time
def worker(name, delay):
print(f"Thread {name} started")
time.sleep(delay)
print(f"Thread {name} finished")
threads = []
for i in range(1, 6):
t = threading.Thread(target=worker, args=(i, i))
threads.append(t)
for t in threads:
t.start()
for t in threads:
t.join()
print("All threads finished")В этом примере создается 5 потоков, каждому из которых передается уникальное имя и время задержки в секундах. Каждый
поток вызывает функцию worker, которая просто печатает, что поток начал работу, засыпает на указанное время и
затем печатает, что поток завершил работу. В главном потоке создаются и запускаются все потоки, а затем используется
метод join для ожидания завершения всех потоков перед тем, как программа завершится.
Python, как и многие другие языки программирования, поддерживает многопроцессорность. Это позволяет распараллеливать выполнение операций и ускорять процессы обработки данных.
Существует несколько способов реализации многопроцессинга в Python:
- Модуль multiprocessing: позволяет создавать и управлять процессами. С помощью него можно создавать процессы, объединять их результаты и передавать данные между процессами.
- Модуль concurrent.futures: предоставляет единый интерфейс для выполнения асинхронных операций и задач в многопроцессорной среде.
- Библиотека asyncio: предназначена для написания асинхронного кода и позволяет выполнять задачи параллельно через корутины.
- Модуль threading: позволяет создавать потоки выполнения, однако в Python из-за особенностей GIL (Global Interpreter Lock) не сильно ускоряет процессы.
Выбор конкретного подхода зависит от задачи, которую необходимо решить.
- Многопоточность:
import threading
def task1():
print("Task 1 executed.")
def task2():
print("Task 2 executed.")
# создаем потоки
t1 = threading.Thread(target=task1)
t2 = threading.Thread(target=task2)
# запускаем потоки
t1.start()
t2.start()
# ждем завершения потоков
t1.join()
t2.join()
print("Tasks completed.")- Многопроцессность:
import multiprocessing
def task1():
print("Task 1 executed.")
def task2():
print("Task 2 executed.")
# создаем процессы
p1 = multiprocessing.Process(target=task1)
p2 = multiprocessing.Process(target=task2)
# запускаем процессы
p1.start()
p2.start()
# ждем завершения процессов
p1.join()
p2.join()
print("Tasks completed.")Заметьте, что одну и ту же задачу в каждом примере выполняют две функции task1() и task2(). Разница лишь в том,
как эти функции запускаются и работают в многопоточной или многопроцессной среде.
Для начала давайте взглянем на следующий код.
"""
Python 3.10 Эта программа показывает (имитирует) выполнение нескольких последовательных задач загрузки,
идущих одна за одной. Имитация потому, что на самом деле ничего не загружается, а время загрузки генерируется
случайно модулем `random` функцией генерации целых чисел `randint`
Название файла '01.последовательная_работа.py'
Version: 0.1
Author: Andrej Marinchenko
Date: 2023-04-11
"""
from random import randint # подключаем функцию генерации целых чисел
from time import time, sleep # подключаем модуль работы со временем
def download_task(filename): # функция-имитация загрузки файла (на входе имя файла)
print('Начать загрузку %s...' % filename) # перед загрузкой выводим сообщение на экран
time_to_download = randint(5, 10) # генерация случайного числа от 5 до 10
sleep(time_to_download) # ждем это случайное время
print('Загрузка завершена! Потребовалось %d секунд' % time_to_download) # выводим сообщение с отображением времени
def main(): # главная функция
start = time() # учитываем время начала программы, значение присваеиваем переменной
download_task('Django.pdf') # запускаем функцию загрузки файла Django.pdf
download_task('PyTorch.epub') # запускаем функцию загрузки файла PyTorch.epub
end = time() # учитываем время конца программы, значение присваеиваем переменной
print('Всего заняло% .2f секунд.' % (end - start)) # сообщение о окончании с вычислением общего времени загрузки
if __name__ == '__main__': # если запущена это программа как главная
main() # запускаем главную функциюНиже приводится результат выполнения программы.
Начать загрузку Django.pdf...
Загрузка завершена! Потребовалось 10 секунд
Начать загрузку PyTorch.epub...
Загрузка завершена! Потребовалось 7 секунд
Всего заняло 17.00 секунд.
Как видно из приведенного выше примера, если код в программе может выполняться только последовательно один за одним по порядку, то даже если выполняются две несвязанные задачи загрузки, необходимо дождаться загрузки одного файла перед запуском следующего. Задача, очевидно, выполняется неэффективно имея значительно-большую вычислительную мощность компьютера. Теперь давайте используем многопроцессорный метод, чтобы поместить две задачи загрузки в разные процессы, код показан ниже.
from multiprocessing import Process # подключаем модуль работы с процессами
from os import getpid # подключаем модуль взять номер процесса операционной системы
from random import randint # модуль генерации случайных чисел
from time import time, sleep # модуль работы со временем
def download_task(filename): # функция загрузки задания (на входе аргумент - имя файла)
print('Номер процесса [%d].' % getpid()) # выводим номер процесса
print('Имя файла %s...' % filename) # выводим имя файла
time_to_download = randint(5, 10) # генерируем случайное число от 5 до 10
sleep(time_to_download) # ожидаем это время
print('%s загружен! За %d секунд' % (filename, time_to_download)) # выводим имя файла и время загрузки
def main(): # главная функция
start = time() # засекаем время начала выполнения работы
p1 = Process(target=download_task, args=('Django.pdf', )) # создаем переменную, отдельный процесс запускающий
# функцию загрузки файла (указываем в классе target)
p1.start() # запускаем этот процесс
p2 = Process(target=download_task, args=('PyTorch.epub', )) # создаем переменную, отдельный процесс запускающий
# функцию загрузки файла
p2.start() # запускаем этот процесс
p1.join() # ожидание окончания процесса
p2.join() # ожидание окончания процесса
end = time() # засекаем время конца выполнения работы
print('Время выполнения составило %.2f секунд.' % (end - start)) # время выполнения 2-х процессов
if __name__ == '__main__': # если запущена это программа как главная
main() # запускаем главную функциюВыполнение приведенного выше кода показывает, что две задачи загрузки запускаются «одновременно», и общее время выполнения программы будет значительно сокращено, и оно больше не является суммой времени двух задач.
В Python функция ветвления процесса реализуется с помощью модуля multiprocessing. Пример кода для запуска функции
ветвления процесса в Python:
import multiprocessing
def worker(name):
"""Функция, которая будет выполняться в отдельном процессе"""
print(f'Привет, я {name}!')
if __name__ == '__main__':
"""Основной процесс"""
p = multiprocessing.Process(target=worker, args=('Вася',))
# запускаем процесс
p.start()
# ожидаем завершение процесса
p.join()В этом примере мы создаем новый процесс с помощью класса Process из модуля multiprocessing. В качестве
параметров мы передаем функцию worker, которая будет выполняться в созданном процессе, и аргументы для этой
функции. Затем мы запускаем процесс с помощью метода start() и ожидаем его завершения с помощью метода join().
Модуль многопроцессорности в Python позволяет одновременно выполнять несколько процессов в разных ядрах процессора для более эффективного использования ресурсов компьютера. Для использования модуля многопроцессорности в Python необходимо выполнить следующие шаги:
- Импортировать модуль multiprocessing:
import multiprocessing- Создать функцию, которую нужно выполнить параллельно.
def function_name(arguments):
# код функции- Создать процесс для выполнения функции.
process = multiprocessing.Process(target=function_name, args=(arguments))- Запустить процесс.
process.start()- Дождаться окончания выполнения процесса.
process.join()- Передать результаты выполнения функции.
result = process.resultМодуль многопроцессорности в Python может использоваться для решения различных задач, например, для распараллеливания обработки больших объемов данных, ускорения вычислений, распределения задач и т.д.
Пул процессов в python позволяет выполнять функции или методы объекта в параллельных процессах. Это особенно полезно для выполнения задач, которые могут выполняться независимо друг от друга и параллельно ускорить работу программы.
Для использования пула процессов в python можно использовать библиотеку multiprocessing. Вот пример использования пула процессов для выполнения функции в нескольких процессах:
import multiprocessing
import time
def func(x):
time.sleep(1)
return x*x
if __name__ == '__main__':
start = time.time()
with multiprocessing.Pool(processes=4) as pool:
results = pool.map(func, range(10))
print(results)
print('Time taken =', time.time()-start)В этом примере мы создаем функцию func, которая принимает аргумент x и возвращает квадрат этого аргумента плюс
задержку в 1 секунду для имитации долгой работы функции. Затем мы создаем пул процессов с помощью multiprocessing. Pool(processes=4), где количество процессов равно 4. Затем мы используем метод pool.map() для применения функции
func к диапазону значений от 0 до 9 в параллельных процессах. Результаты сохраняются в переменной results. В
конце мы выводим результаты и время выполнения программы.
Обратите внимание на использование if __name__ == '__main__', которое необходимо для безопасного запуска программы
в параллельных процессах в операционной системе Windows.
Мы также можем использовать классы и функции в модуле подпроцесса для создания и запуска подпроцесса, а затем связываться с подпроцессом через конвейер. Далее мы сосредоточимся на том, как добиться связи между двумя процессами. Мы запускаем два процесса: один вывод Ping, один вывод Pong, два процесса выводят Ping и Pong всего 10. Звучит просто, но писать так было бы неправильно.
"""
Python 3.10 Используйте класс Process для создания нескольких процессов
Название файла '02.много_процесс.py'
Version: 0.1
Author: Andrej Marinchenko
Date: 2023-04-13
"""
# Результат выполнения следующей программы может подтвердить, что родительский процесс скопировал процесс и его
# структуру данных при создании дочернего процесса
# Каждый процесс имеет собственное независимое пространство памяти, поэтому обмен данными между процессами может
# осуществляться только через IPC
import random # подключаем функцию генерации целых чисел
from multiprocessing import Process, Queue, current_process # работа с множеством процессов
from time import sleep # функция ожидания
def sub_task(content, counts): # функция выполнения задания (на входе 2 аргумента строка и число)
print(f'PID: {current_process().pid}') # выводим номер процесса
counter = 0 # создаем переменную равную 0
while counter < counts: # цикл пока число меньше принятого аргумента (от 5 до 10 сгенир. ниже)
counter += 1 # увеличиваем значение перемнной на 1
print(f'{counter}: {content}') # печать числа и строки
sleep(0.01) # время ожидания - сотая секунда
def main(): # главная функция
number = random.randrange(5, 10) # генерация случайного числа от 5 до 10 присваиваем переменной number
Process(target=sub_task, args=('Ping', number)).start() # создаем и запускаем процесс, указываем имя запускаемой
# функции, а также передаваемые аргументы, строку и число
Process(target=sub_task, args=('Pong', number)).start() # создаем и запускаем процесс, указываем имя запускаемой
# функции, а также передаваемые аргументы, строку и число
if __name__ == '__main__': # если запущена это программа как главная
main() # запускаем главную функциюВ ранней версии Python для реализации многопоточного программирования был представлен модуль потока (теперь он называется _thread). Однако этот модуль слишком низкоуровневый, и многие функции не предоставляются. Поэтому мы рекомендуем использовать модуль потоковой передачи для текущего процесса - многопоточная разработка. Модуль обеспечивает лучшую объектно-ориентированную инкапсуляцию для многопоточного программирования. Пример многопоточного приложения:
import threading
def print_numbers():
for i in range(10):
print(i)
def print_letters():
for letter in ['a', 'b', 'c', 'd', 'e']:
print(letter)
t1 = threading.Thread(target=print_numbers)
t2 = threading.Thread(target=print_letters)
t1.start() # Запускаем первый поток
t2.start() # Запускаем второй поток
t1.join() # Ждем, пока первый поток выполнится
t2.join() # Ждем, пока второй поток выполнитсяВ этом примере мы создаем две функции, которые будут выполняться в разных потоках. Мы используем модуль threading, чтобы создать два объекта потока и передать им функции, которые они должны выполнить.
Затем мы запускаем каждый поток методом start(), чтобы они начали исполнение. После этого мы вызываем метод join() для каждого потока, чтобы дождаться, пока они завершат свою работу.
Давайте реализуем пример загрузки файла в многопоточном режиме.
from threading import Thread # подключаем модуль многопоточной работы
from random import randint # подключаем функцию генерации целых чисел
from time import time, sleep # подключаем модуль работы со временем
def download_task(filename): # функция-имитация загрузки файла (на входе имя файла)
print('Начать загрузку %s...' % filename) # перед загрузкой выводим сообщение на экран
time_to_download = randint(5, 10) # генерация случайного числа от 5 до 10
sleep(time_to_download) # ждем это случайное время
print('Загрузка завершена! Потребовалось %d секунд' % time_to_download) # выводим сообщение с отображением времени
def main(): # главная функция
start = time() # время начала выполнения программы
thread1 = Thread(target=download_task, args=('Django.pdf',)) # создаем поток
thread1.start() # начало выполнения потока
thread2 = Thread(target=download_task, args=('PyTorch.epub',)) # создаем поток
thread2.start() # начало выполнения потока
thread1.join() # ожидание выполнения
thread2.join() # ожидание выполнения
end = time() # время конца выполнения программы
print('Всего потребовалось %.3f секунд..' % (end - start)) # сообщение о окончании с вычислением общего времени загрузки
if __name__ == '__main__': # если запущена это программа как главная
main() # запускаем главную функциюМы можем напрямую использовать Thread класс модуля потоковой передачи для создания потоков, но мы говорили об очень важной концепции, называемой «наследование». Мы можем создавать новые классы из существующих классов, поэтому мы также можем создавать собственные классы Thread, наследуя их. После создания класса потока, затем мы можем создать объект потока и запустить его. Код показан ниже.
import threading # подключаем модуль многопоточной работы
from random import randint # подключаем функцию генерации целых чисел
from time import time, sleep # подключаем модуль работы со временем
class DownloadTask(threading.Thread): # создаем класс
def __init__(self, filename): # инициализация класса
super().__init__()
self._filename = filename
def run(self): # метод выполнения
print('Начать загрузку %s...' % self._filename) # перед загрузкой выводим сообщение на экран
time_to_download = randint(5, 10) # генерация случайного числа от 5 до 10
print('Оставшееся время %d секунд.' % time_to_download) # выводим сообщение с отображением ост. времени
sleep(time_to_download) # ждем это случайное время
print('%s загрузка завершена!' % self._filename) # выводим сообщение с отображением имени файла
def main(): # главная функция
start = time() # время начала выполнения программы
# Поместите несколько задач загрузки в несколько потоков для выполнения
# . После запуска потока он обратится к нему и выполнит
# метод run.
thread1 = DownloadTask('Python.pdf') # Создаем объект потока через собственный класс потока
thread1.start() # начало выполнения потока
thread2 = DownloadTask('Peking.avi') # Создаем объект потока через собственный класс потока
thread2.start() # начало выполнения потока
thread1.join() # ожидание выполнения
thread2.join() # ожидание выполнения
end = time() # время конца выполнения программы
print('Всего потребовалось %.3f секунд..' % (end - start)) # сообщение о окончании с вычислением общего времени
# загрузки
if __name__ == '__main__': # если запущена это программа как главная
main() # запускаем главную функцию
# Обратите внимание, что потоки, созданные с помощью threading.Thread, по умолчанию не являются потоками демонаПоскольку несколько потоков могут совместно использовать пространство памяти процесса, относительно просто реализовать связь между несколькими потоками. Самый простой способ, который можно придумать - это установить глобальную переменную, и несколько потоков могут совместно использовать эту глобальную переменную. Но когда несколько потоков совместно используют одну и ту же переменную (мы обычно называем ее «ресурсом»), это может привести к неконтролируемым результатам, что приведет к сбою программы или даже к сбою. Если ресурс конкурирует за использование несколькими потоками, то мы обычно называем его «критическим ресурсом», и доступ к «критическому ресурсу» должен быть защищен, иначе ресурс будет в «хаотическом» состоянии. В следующем примере демонстрируется сценарий, в котором 100 потоков переводят деньги на один и тот же банковский счет (переводят 1 рубль). В этом примере банковский счет является критическим ресурсом, и мы можем получить ошибки если нет защиты.
"""
Python 3.10 Несколько потоков обмениваются данными без блокировки
Название файла '05.многопоточная_работа.py'
Version: 0.1
Author: Andrej Marinchenko
Date: 2023-04-13
"""
from time import sleep # подключаем модуль работы со временем
from threading import Thread, Lock # подключаем модуль многопоточной работы
class Account(object): # создаем класс
def __init__(self): # инициализация класса
self._balance = 0
self._lock = Lock()
def deposit(self, money): # метод щет (на входе сумма денег)
# Получить блокировку перед выполнением последующего кода
self._lock.acquire()
try:
new_balance = self._balance + money
sleep(0.01)
self._balance = new_balance
finally:
# Этот код помещается наконец, чтобы гарантировать, что операция снятия блокировки должна быть выполнена
self._lock.release()
@property # спец.метод (свойство) геттер или аксессор (устанавливает значение из вне класса)
def balance(self): # метод имени (т.е. при обращении к методу имени - вы получите значение этого атрибута)
return self._balance # возвращает значение атрибута имени
class AddMoneyThread(Thread): # создаем класс
def __init__(self, account, money): # инициализация класса (на входе аргументы имя аккаунта и количество денег)
super().__init__()
self._account = account # создаем атрибут равный аргументу аккаунта
self._money = money # создаем атрибут равный аргументу денег
def run(self): # метод выполнение перевода
self._account.deposit(self._money)
def main(): # главная функция
account = Account() # создаем объект класса аккаунт
threads = [] # создаем пустой список
# Создаем 100 потоков депозита для внесения денег на тот же счет
for _ in range(100): # в диапазоне от 1 до 100
t = AddMoneyThread(account, 1) # создаем объект класса процесс добавить деньги (передаем имя акк, 1 рубль)
threads.append(t)
t.start() # запускаем процесс
# Дождитесь выполнения всех потоков депозита
for t in threads: # цикл перечисления всех потоков
t.join() # ожидает выполнение потока
print('Щет пополнен, текущий баланс составляет: %d рублей' % account.balance)
if __name__ == '__main__': # проверка основной программы
main() # запуск главной функцииПри запуске вышеуказанной программы результат удивителен: каждый из 100 потоков переводит 1 рубль на счет, результат на самом деле намного меньше 100 рублей. Это происходит потому, что мы не защитили «критический ресурс» банковских счетов. Когда несколько потоков вносят деньги на счет одновременно, new_balance = self._balance + money эта строка кода будет выполняться одновременно, и все балансы счетов, полученные несколькими потоками, не изменятся, поэтому операция +1 рубль в отдельных потоках не была выполнена, и поэтому был получен неправильный результат. В этом случае метод многопоточности блокировки «Lock» может пригодиться. Мы можем защитить «критические ресурсы» с помощью «блокировок». Только потоки, получившие «блокировку», могут получить доступ к «критическим ресурсам», а другие потоки, которые не получили «блокировку», могут быть заблокированы только до тех пор, пока поток, имеющий полученная «блокировка» не будет снят. Следующий код демонстрирует, как использовать «блокировку» для защиты операции банковского счета, чтобы получить правильный результат.
"""
Python 3.10 Несколько потоков обмениваются данными с блокировкой
Название файла '06.многопоточная_работа.py'
Version: 0.1
Author: Andrej Marinchenko
Date: 2023-04-13
"""
import time # подключаем модуль работы со временем
import threading # подключаем модуль многопоточной работы
class Account(object): # создаем класс
def __init__(self): # инициализация класса
self._balance = 0
self._lock = threading.Lock()
def deposit(self, money): # метод депозит (на входе сумма денег)
# Код может продолжить выполнение после получения блокировки
self._lock.acquire()
try:
new_balance = self._balance + money
time.sleep(0.01)
self._balance = new_balance
finally:
# После завершения операции не забудьте снять блокировку
self._lock.release()
@property # спец.метод (свойство) геттер или аксессор (устанавливает значение из вне класса)
def balance(self): # метод имени (т.е. при обращении к методу имени - вы получите значение этого атрибута)
return self._balance # возвращает значение атрибута имени
if __name__ == '__main__': # проверка основной программы
account = Account()
# Создайте 100 потоков депозита для внесения денег на тот же счет
for _ in range(100):
threading.Thread(target=account.deposit, args=(1,)).start()
# Дождитесь выполнения всех потоков депозита
time.sleep(2)
print('Щет пополнен, текущий баланс составляет: %d рублей' % account.balance)Еще более прискорбно то, что многопоточность Python не использует преимущества многоядерных характеристик ЦП. Это можно подтвердить, запустив несколько потоков, выполняющих бесконечный цикл. Причина этого в том, что интерпретатор Python имеет «глобальную блокировку интерпретатора» (GIL). Перед выполнением любого потока необходимо получить блокировку GIL, а затем каждый раз, когда выполняется 100 байт кода, интерпретатор автоматически освобождает GIL. lock., позволяя другим потокам иметь возможность выполняться, это историческая проблема, но даже в этом случае, как мы приводили пример ранее, использование многопоточности по-прежнему положительно с точки зрения повышения эффективности выполнения и улучшения взаимодействия с пользователем.
Python поддерживает множество методов для межпроцессного взаимодействия. Вот некоторые из них:
- Модуль
multiprocessing- используется для создания и управления процессами. С помощьюmultiprocessing.Pipe()можно создать канал для обмена данными между двумя процессами.
from multiprocessing import Process, Pipe
def send(conn):
conn.send('Hello')
conn.close()
def receive(conn):
print(conn.recv())
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
p1 = Process(target=send, args=(child_conn,))
p2 = Process(target=receive, args=(parent_conn,))
p1.start()
p2.start()
p1.join()
p2.join()- Модуль
subprocess- используется для запуска новых процессов. Командаsubprocess.check_output()запускает процесс и возвращает его вывод.
import subprocess
result = subprocess.check_output(['ls', '-l'])
print(result.decode())- Модуль
os- используется для доступа к системным функциям, таким как создание и управление процессами. Можно использовать функциюos.fork()для создания дочернего процесса.
import os
pid = os.fork()
if pid == 0:
print('Child process')
else:
print('Parent process')- Модуль
socket- используется для сетевого взаимодействия между процессами на разных устройствах. Можно создать TCP-соединение между процессами.
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('localhost', 5555))
server_socket.listen(1)
client_socket, address = server_socket.accept()
print('Connection from', address)
while True:
data = client_socket.recv(1024)
if not data:
break
print('Received:', data.decode())
client_socket.sendall(data)
client_socket.close()
server_socket.close()Вот простой пример использования модуля socket в Python для потоковой передачи данных:
import socket
# Создаем серверный сокет и связываем его с IP-адресом и портом
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('127.0.0.1', 8000))
# Слушаем входящие соединения
server_socket.listen(1)
print('Ожидаем соединения...')
# Принимаем соединение
client_socket, addr = server_socket.accept()
print(f'Успешное соединение с {addr}')
# Отправляем данные
client_socket.send(b'Hello, World!')
# Получаем ответ
data = client_socket.recv(1024)
print(f'Получено: {data}')
# Закрываем соединения
client_socket.close()
server_socket.close()Этот код создает серверный сокет и связывает его с IP-адресом и портом. Затем он ожидает входящие соединения и принимает соединение от клиента. Далее отправляет данные клиенту и получает ответ. После этого он закрывает соединения.
Обратите внимание, что этот код представлен только в качестве примера и не является полным решением для потоковой передачи данных.
- Shared Memory (multiprocessing) - это метод, который позволяет двум или более процессам обмениваться данными через общую память. Этот метод быстр, но немного сложен в использовании.
Пример использования межпроцессного взаимодействия с помощью Shared Memory (Общей памяти) в Python c помощью модуля multiprocessing:
import multiprocessing
def write_shared_value(value, shared_value):
"""Записывает значение в общую память."""
shared_value.value = value
def read_shared_value(shared_value):
"""Читает значение из общей памяти."""
return shared_value.value
if __name__ == "__main__":
# Создаем общую память с начальным значением "None"
shared_value = multiprocessing.Value('i', None)
# Создаем процессы, передаем им ссылку на общую память
process1 = multiprocessing.Process(target=write_shared_value, args=(10, shared_value))
process2 = multiprocessing.Process(target=write_shared_value, args=(20, shared_value))
process3 = multiprocessing.Process(target=write_shared_value, args=(30, shared_value))
# Запускаем процессы
process1.start()
process2.start()
process3.start()
# Ожидаем завершения процессов
process1.join()
process2.join()
process3.join()
# Читаем значение из общей памяти
print("Значение, записанное в общую память: {}".format(read_shared_value(shared_value)))Код создает общую память с начальным значением
- Message Queues (multiprocessing) - это очередь сообщений, которую можно использовать для передачи сообщений между процессами. Этот метод удобен для передачи больших объемов данных. Message Queues - это способ передачи сообщений между процессами через разделяемый объект очереди.
Вот пример использования Message Queues через multiprocessing:
import multiprocessing
# Функция отправки сообщения
def send_message(queue):
message = "Привет, мир!"
queue.put(message) # Добавляем сообщение в очередь
print("Отправлено сообщение:", message)
# Функция получения сообщения
def receive_message(queue):
message = queue.get() # Получаем сообщение из очереди
print("Получено сообщение:", message)
if __name__ == "__main__":
queue = multiprocessing.Queue() # Создаем очередь
process1 = multiprocessing.Process(target=send_message, args=(queue,))
process2 = multiprocessing.Process(target=receive_message, args=(queue,))
process1.start() # Запускаем процесс отправки сообщения
process2.start() # Запускаем процесс получения сообщения
process1.join() # Ждем завершения процесса отправки сообщения
process2.join() # Ждем завершения процесса получения сообщенияВ этом примере мы создаем два процесса send_message и receive_message, каждый процесс работает с одной и той же
очередью. После того, как сообщение было помещено в очередь с помощью queue.put(message), процесс получения
сообщения вызывает queue.get(), чтобы получить сообщение из очереди.
Важно заметить, что во избежание проблем с совместным использованием ресурсов эти два процесса запускаются
одновременно в разных процессах, это делается с помощью проверки условия if __name__ == "__main__":.
Это простой пример, который демонстрирует использование multiprocessing и Message Queues для межпроцессного
взаимодействия. Однако, есть и другие способы межпроцессного взаимодействия, такие как shared memory и pipes,
которые могут быть лучше подходят для конкретных сценариев.
- RPC (xmlrpclib) - это протокол удаленной процедуры вызова, который позволяет вызывать функции в одном процессе из другого процесса. Этот метод удобен для передачи сложных объектов данных.
Пример использования межпроцессного взаимодействия в Python с использованием RPC (Remote Procedure Call) и библиотеки xmlrpclib:
Серверный код:
from xmlrpc.server import SimpleXMLRPCServer
from xmlrpc.server import SimpleXMLRPCRequestHandler
# Определяем функцию, которую можно вызывать удаленно
def add_numbers(x, y):
return x + y
# Определяем обработчик запросов и запускаем сервер
class RequestHandler(SimpleXMLRPCRequestHandler):
rpc_paths = ('/RPC2',)
def __init__(self, request, client_address, server):
super().__init__(request, client_address, server)
self.allow_none = True
with SimpleXMLRPCServer(('localhost', 8000),
requestHandler=RequestHandler) as server:
server.register_function(add_numbers, 'add_numbers')
server.serve_forever()Клиентский код:
import xmlrpc.client
# Создаем прокси-объект для обращения к удаленному серверу
with xmlrpc.client.ServerProxy("http://localhost:8000/") as proxy:
# Вызываем удаленную функцию
result = proxy.add_numbers(4, 5)
# Выводим результат
print("4 + 5 = ", result)При запуске серверного кода будет запущен XML-RPC сервер на локальной машине на порту 8000. После этого клиентский
код сможет вызывать функцию add_numbers на этом сервере, передавая ей необходимые параметры. Результат выполнения
функции будет возвращен клиенту.
Независимо от того, является ли это многопроцессорным или многопоточным, если их количество велико, эффективность определенно не улучшится. Почему? Давайте проведем аналогию. Предположим, вы, к сожалению, готовитесь к вступительным экзаменам в старшую школу. Каждую ночь вам нужно выполнять 5 домашних заданий по русскому, математике, английскому языку, физике и химии. Каждое домашнее задание занимает 1 час. Если вы сначала потратите 1 час на выполнение домашнего задания по русскому языку, после его выполнения потратите 1 час на домашнее задание по математике, чтобы вы выполнили все одно за другим, а в целом это займет 5 часов. Этот метод называется однозадачным. Если вы планируете переключиться на модель многозадачности, вы можете 1 минуту изучить китайский язык, затем переключиться на домашнее задание по математике, выполнить 1 минуту, затем переключиться на английский и т. д. Если скорость переключения достаточно высока, этот метод будет выполнять с одноядерным процессором. Многозадачность такая же. С точки зрения наблюдателя, вы пишете 5 домашних заданий одновременно.
Однако за переключение домашнего задания приходится платить. Например, чтобы переключиться с русского на математику, вы должны сначала запомнить что уже выполнили и очистить стол (это называется сохранением сцены), затем прежде чем вы начнете делать домашнее задание по математике Вам необходимо открыть учебник и найти циркуль и линейку (это называется подготовкой к новой среде). Операционная система одинакова при переключении процессов или потоков. Она должна сохранить текущую среду выполнения (состояние регистров ЦП, страницу памяти и т. д.), А затем подготовить среду выполнения новой задачи (восстановить последнее состояние регистра, переключить страницы памяти и т. д.) до начала выполнения. Хотя этот процесс переключения происходит быстро, он также требует времени. Если одновременно выполняются тысячи задач, операционная система может быть в основном занята переключением задач, и на выполнение задач совсем не так много времени. Наиболее распространенная ситуация - жужжание жесткого диска, отсутствие окна ответить, и система находится в состоянии приостановки анимации. Рессурс Вашего компьютера исчерпан, он не безграничен, Ваш компьютер начинает тормозить, лагать или даже зависнуть. Следовательно, как только многозадачность достигает предела, это приведет к резкому падению производительности системы и, в конечном итоге, к сбою всех задач.
Второе соображение относительно того, следует ли использовать многозадачность - это тип задачи, который можно разделить на вычислительно-интенсивные и с интенсивным вводом-выводом. Характерной чертой задач с интенсивными вычислениями является выполнение большого количества вычислений и использование ресурсов ЦП, таких как кодирование и декодирование видео или преобразование формата и т. д. Эта задача зависит от вычислительной мощности ЦП. Хотя она также может быть выполнена с помощью многозадачности. Чем больше задача - тем больше времени затрачивается на переключение задач, тем ниже эффективность ЦП для выполнения задач. Вычислительные задачи в основном потребляют ресурсы ЦП. Эффективность выполнения таких задач с помощью языков сценариев, таких как Python, обычно очень низкая. Язык C является наиболее подходящим для таких задач. Ранее мы упоминали, что Python имеет встроенный код C / C ++.
Класс потока (thread) в Python используется для создания параллельных процессов и выполнения задач в фоновом режиме.
Для создания класса потока в Python необходимо использовать модуль threading. Пример создания класса потока:
import threading
class MyThread(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def run(self):
print("Thread %s is running" % self.name)В этом примере создается класс MyThread, который наследуется от класса Thread модуля threading. В методе init класса MyThread определяется имя потока, а в методе run – выполняется код, который должен быть выполнен параллельно.
Чтобы запустить поток, необходимо создать его экземпляр и вызвать метод start:
my_thread = MyThread("Thread 1")
my_thread.start()Это запустит поток MyThread с именем "Thread 1", который выполнит метод run.
Вот пример класса потока:
import threading
class MyThread(threading.Thread):
def __init__(self, thread_id, name):
threading.Thread.__init__(self)
self.thread_id = thread_id
self.name = name
def run(self):
print("Starting " + self.name)
# Делаем то, что нужно в потоке
print("Exiting " + self.name)
# Создаем экземпляр класса и запускаем поток
thread1 = MyThread(1, "Thread 1")
thread1.start()
# Ожидаем завершения потока
thread1.join()
print("Exiting Main Thread")В этом примере определен класс MyThread, унаследованный от класса threading.Thread. В методе __init__ мы
инициализируем идентификатор и имя потока. В методе run мы определяем, что должен делать поток при запуске - в
данном примере, просто выводим сообщения в консоль.
Для запуска потока мы создаем экземпляр класса и вызываем метод start. Затем мы ожидаем завершения потока с
помощью метода join.
В Python блокировка может быть выполнена при помощи модуля threading. Например, вот простая реализация блокировки:
import threading
class Lock:
def __init__(self):
self.lock = threading.Lock()
def acquire(self):
self.lock.acquire()
def release(self):
self.lock.release()Здесь мы создаем класс Lock, который использует встроенный класс Lock из модуля threading. Методы acquire и
release просто вызывают соответствующие методы класса Lock.
Вызов метода acquire блокирует поток до тех пор, пока другой поток не вызовет release. Это может быть полезно
при выполнении критических секций, когда несколько потоков могут конкурировать за доступ к общим ресурсам.
В Python потоковая передача данных может осуществляться с помощью модуля multiprocessing. Класс условия для
потоковой передачи данных - Condition.
Condition - это объект, который позволяет потокам взаимодействовать друг с другом при работе с общим ресурсом. Он
предоставляет методы wait(), notify() и notify_all() для синхронизации процессов.
Пример использования класса Condition:
import threading
condition = threading.Condition()
def producer():
with condition:
condition.notify_all() # уведомление всех потоков о готовности данных
condition.wait() # ожидание пока другой поток не получит данные
def consumer():
with condition:
condition.wait() # ожидание получения данных
condition.notify() # уведомление о получении данныхВ данном примере поток producer уведомляет другой поток о готовности данных методом notify_all(), а затем ждет,
пока поток consumer не получит данные методом wait(). Поток consumer, в свою очередь, ждет на условии
получения данных методом wait() и уведомляет producer о получении данных методом notify().
В Python пул потоков (thread pool) представляет группу потоков, которые готовы выполнить определенную функцию. Вместо того, чтобы создавать новый поток каждый раз, когда необходимо выполнить асинхронную задачу, можно использовать пул потоков, чтобы переиспользовать существующие потоки и снизить накладные расходы на создание новых потоков.
В Python пул потоков может быть реализован, например, с использованием библиотеки concurrent.futures. Ее класс
ThreadPoolExecutor предоставляет интерфейс для запуска асинхронных задач в пуле потоков.
Пример использования ThreadPoolExecutor:
import concurrent.futures
def my_task():
# выполнение задачи...
# создаем пул потоков с максимальным количеством потоков равным 5
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# добавляем задачу в очередь на выполнение в пуле
future = executor.submit(my_task)
# получаем результат выполнения задачи (если он есть)
result = future.result()Здесь мы создаем пул потоков с максимальным количеством потоков равным 5. Затем добавляем задачу в очередь на
выполнение в пуле с помощью метода submit(). Получаем результат выполнения задачи (если он есть) с помощью метода
result().
Важно заметить, что использование пула потоков не всегда приводит к ускорению выполнения программы. Скорость выполнения задач в пуле потоков может зависеть от характеристик процессора и доступных ресурсов, поэтому необходимо тщательно тестировать и оптимизиров
Помимо задач, требующих большого объема вычислений, другие задачи, связанные с вводом-выводом сети и носителей, могут рассматриваться как задачи с интенсивным вводом-выводом. Этот тип задач характеризуется низким потреблением ресурсов ЦП, и большая часть времени ожидания операции ввода-вывода уже завершена (потому что скорость ввода-вывода намного ниже скорости процессора и памяти). Для задач с интенсивным вводом-выводом, если вы начнете многозадачность, вы можете уменьшить время ожидания ввода-вывода и позволить ЦП работать эффективно. Существует большой класс задач, связанных с интенсивным вводом-выводом, включая сетевые приложения и веб-приложения, в которых мы скоро будем участвовать.
Самым важным в улучшении операций ввода-вывода в современных операционных системах является поддержка асинхронного ввода-вывода. Если вы в полной мере используете поддержку асинхронного ввода-вывода, предоставляемую операционной системой, вы можете использовать однопроцессную однопоточную модель для выполнения многозадачности. Эта новая модель называется моделью, управляемой событиями. Nginx - это веб-сервер, поддерживающий асинхронный ввод-вывод. Он использует однопроцессную модель на одноядерном ЦП для эффективной поддержки многозадачности. На многоядерном ЦП вы можете запускать несколько процессов (число совпадает с числом ядер ЦП), полностью используя многоядерный ЦП. Серверные программы, разработанные с помощью Node.js, также используют этот рабочий режим, который также является популярным решением для параллельного программирования.
На языке Python модель программирования с одним потоком и асинхронным вводом-выводом называется сопрограммой. Благодаря поддержке сопрограммы можно писать эффективные многозадачные программы, основанные на событиях. Самым большим преимуществом сопрограмм является чрезвычайно высокая эффективность выполнения, поскольку переключение подпрограмм - это не переключение потоков, а управляется самой программой, поэтому нет накладных расходов на переключение потоков. Второе преимущество сопрограммы заключается в том, что она не требует многопоточного механизма блокировки, потому что существует только один поток и нет конфликта записи переменных одновременно. В сопрограмме общие ресурсы не нуждаются в блокировке, и для оценки требуется только состояние выполнения. Эффективность намного выше, чем многопоточность. Если вы хотите в полной мере использовать многоядерность ЦП, самый простой способ - это многопроцессорность + асихронность, которая не только полностью использует многоядерность, но и дает полную возможность продемонстрировать высокую эффективность сопрограммы для получения чрезвычайно высокой производительности.
В Python существует модуль asyncio, который позволяет писать асинхронный код с помощью async / await. Он
предоставляет различные функции для организации асинхронного ввода / вывода.
Для однопоточного асинхронного ввода / вывода в Python следуйте примеру ниже:
import asyncio
async def main():
reader, writer = await asyncio.open_connection('localhost', 8888) #открываем соединение
writer.write(b'Hello, server!') #отправляем сообщение на сервер
await writer.drain() #ожидаем, пока сообщение не будет отправлено
data = await reader.read(100) #получаем ответ от сервера
print(f'Received: {data.decode()}') #выводим ответ на консоль
writer.close() #закрываем соединение
await writer.wait_closed()
asyncio.run(main()) #запускаем асинхронную функциюОбратите внимание, что writer.write() отправляет байтовую строку на сервер, а reader.read() считывает до 100
байт ответа.
Такой код N-раз будет начинать новый контекст корутины, каждый раз создавая часть состояний и ресурсов, что может работать использоваться c боязнью гонок при одновременном доступе к ресурсам, что и является минусам.
Вот пример с использованием асинхронного цикла вместо вызова asyncio.run():
import asyncio
async def main():
reader, writer = await asyncio.open_connection('localhost', 8888) #открываем соединение
writer.write(b'Hello, server!') #отправляем сообщение на сервер
await writer.drain() #ожидаем, пока сообщение не будет отправлено
data = await reader.read(100) #получаем ответ от сервера
print(f'Received: {data.decode()}') #выводим ответ на консоль
writer.close() #закрываем соединение
await writer.wait_closed()
asyncio.get_event_loop().run_until_complete(main()) #запускаем асинхронную функцию в циклеЭтот код запускает main() с помощью цикла событий asyncio. Этот подход позволяет более эффективно использовать
ресурсы и избежать проблем с гонкой.
В интерфейсе, показанном ниже, есть две кнопки: «Загрузить» и «О программе». Загрузка файлов в Интернете займет 10 секунд, сама кнопка «Загрузить» станет не доступна для нажатия. Если вы не используете «многопоточность». , мы будем. Было обнаружено, что при нажатии кнопки «Загрузить» другие части всей программы блокируются этой трудоемкой задачей и не могут быть выполнены. Это, очевидно, очень неприятно для пользователя. Код показан ниже.
"""
Python 3.10 При использовании одного потока запускаем GUI приложение.
Название файла '07.однопоточная_работа.py'
Version: 0.1
Author: Andrej Marinchenko
Date: 2023-04-13
"""
import time # модуль работы со временем
import tkinter # модуль работы с GUI-приложения
import tkinter.messagebox # модуль работы с сообщениями GUI
def download(): # функция имитации загрузки в 10 секунд при нажатии на кнопку загрузить
time.sleep(10) # время ожидания 10 секунд
tkinter.messagebox.showinfo('подсказка', 'загрузка завершена!') # выводим сообщение в отдельное окно
def show_about(): # функция вывести сообщение при нажатии на кнопку О программе
tkinter.messagebox.showinfo('О программе', 'Автор: Андрей (v1.0)')
def main(): # главная функция
top = tkinter.Tk() # создаем объект графического приложения
top.title('Программа загрузки') # название окна GUI
top.geometry('200x150') # размер окна GUI
top.wm_attributes('-topmost', True) # положение окна
panel = tkinter.Frame(top) # создаем панель с кнопками
button1 = tkinter.Button(panel, text='Загрузить', command=download) # прописываем кнопку
button1.pack(side='left') # размещаем ее на панели
button2 = tkinter.Button(panel, text='О программе', command=show_about) # прописываем кнопку
button2.pack(side='right') # размещаем ее на панели
panel.pack(side='bottom') # обрабатываем нашу панель кнопок
tkinter.mainloop() # обработчик нашего окна
if __name__ == '__main__': # если запущена это программа как главная
main() # запускаем главную функцию
# После того, как вы нажмете кнопку загрузки без использования многопоточности, эта операция займет 10 секунд
# Весь основной цикл сообщений также будет заблокирован на 10 секунд и не сможет реагировать на другие события
# На самом деле последовательное выполнение подзадач без причинно-следственной связи нецелесообразноЕсли вы используете многопоточность для помещения трудоемких задач в отдельный поток для выполнения, чтобы основной поток не был заблокирован трудоемкими задачами, то измененный код показан ниже.
"""
Python 3.10 При использовании нескольких потоков трудоемкие задачи выполняются в отдельных потоках.
Название файла '08.многопоточная_работа.py'
Version: 0.1
Author: Andrej Marinchenko
Date: 2023-04-13
"""
import time # подключаем модуль времени
import tkinter # модуль работы с GUI
import tkinter.messagebox # модуль работы с GUI сообщениями
from threading import Thread # подключаем модуль многопоточной работы
def main(): # главная функция
class DownloadTaskHandler(Thread):
def run(self):
# Имитация задачи загрузки занимает 10 секунд
time.sleep(10)
tkinter.messagebox.showinfo('подсказка', 'загрузка завершена!')
# Включить кнопку загрузки
button1.config(state=tkinter.NORMAL)
def download(): # функция загрузки (в процессе загрузки ещер раз на кнопку загрузить нельзя)
# Отключить кнопку загрузки
button1.config(state=tkinter.DISABLED)
# Установите поток как поток демона через параметр демона (основная программа больше не будет продолжать выполнение при выходе)
DownloadTaskHandler(daemon=True).start()
def show_about(): # функция кнопки о программе
tkinter.messagebox.showinfo('О программе', 'Автор: Андрей (v1.0)')
top = tkinter.Tk()
top.title('Загрузка информации')
top.geometry('200x150')
top.wm_attributes('-topmost', 1)
panel = tkinter.Frame(top)
button1 = tkinter.Button(panel, text='Загрузить', command=download)
button1.pack(side='left')
button2 = tkinter.Button(panel, text='О программе', command=show_about)
button2.pack(side='right')
panel.pack(side='bottom')
tkinter.mainloop()
if __name__ == '__main__': # проверка основной программы
main() # запуск главной функцииДавайте выполним трудоемкую задачу по суммированию 1 ~ 100000000. Эта задача очень проста и может быть решена с небольшим знанием циклов. Код показан ниже.
"""
Python 3.10 Вычисление последовательно в один поток.
Название файла '09.однопоточная_работа.py'
Version: 0.1
Author: Andrej Marinchenko
Date: 2023-04-13
"""
from time import time # подключаем модуль времени
def main(): # главная функция
total = 0 # создаем переменную равную 0
number_list = [x for x in range(1, 100000001)] # создаем список с значенями от 1 до 100 000 000
start = time() # засекаем время начала запуска программы
for number in number_list: # перебираем все элементы списки
total += number # добовляем к переменной каждый элемент списка
print(total) # выводим результат суммирования на экран
end = time() # засекаем время конца программы
print('Execution time: %.3fs' % (end - start)) # выводим время на суммирование на экран
if __name__ == '__main__': # проверка основной программы
main() # запуск главной функцииВ приведенном выше коде я намеренно создал контейнер списка и заполнил 100 000 000 чисел. Этот шаг на самом деле занимает много времени, поэтому ради справедливости, мы разобъем эту задачу на 8 процессов. При этом мы не рассматриваем время, затраченное на операцию нарезки списка в данный момент, но учитывается только время операции и комбинированный результат операции. Код выглядит следующим образом.
"""
Python 3.10 Вычисление последовательно в 8 процессов.
Название файла '10.многопроцессорная_работа.py'
Version: 0.1
Author: Andrej Marinchenko
Date: 2023-04-13
"""
from multiprocessing import Process, Queue
from time import time
def task_handler(curr_list, result_queue):
total = 0
for number in curr_list:
total += number
result_queue.put(total)
def main(): # главная функция
processes = []
number_list = [x for x in range(1, 100000001)]
result_queue = Queue()
index = 0
# разбиваем задачу на 8 процессов
for _ in range(8):
p = Process(target=task_handler,
args=(number_list[index:index + 12500000], result_queue))
index += 12500000
processes.append(p)
p.start()
start = time() # время начала выполнения программы
for p in processes:
p.join() # ожидаем окончание каждого процесса
total = 0
while not result_queue.empty():
total += result_queue.get()
print(total)
end = time()
print('Execution time: ', (end - start), 's', sep='')
if __name__ == '__main__': # проверка основной программы
main() # запуск главной функцииСравните результаты выполнения двух фрагментов кода, ранее приведенный код занимает около 6 секунд, тогда как последний код занимает менее 1 секунды. Опять же, мы просто сравнили время расчета, не учитывая время, затрачиваемое на создание списка и операции нарезки. После использования нескольких процессов достигается большее время выполнения ЦП и лучшее использование многоядерных характеристик ЦП, что значительно сокращает время выполнения программы и тем больше объем вычислений большой эффект более очевиден. Конечно, при желании вы можете развернуть несколько процессов на разных компьютерах для создания распределенного процесса. Конкретный метод заключается в том, чтобы предоставить общий доступ к объекту через сеть через multiprocessing. managers диспетчер, предоставленный в модуле Queue (зарегистрируйтесь в сети, чтобы другие компьютеры имели доступ), эта часть контента также остается на усмотрение поисковых роботов.