Отчёт по лабораторной работе №6: Потоки. Процессы. Асинхронность.
Ссылка на Google Colab: https://colab.research.google.com/drive/1yBGumK0AY-woG3ddKYJeUSQnf0KswZhz?usp=sharing
1. Цель работы
Целью работы является изучение механизмов параллельного и конкурентного выполнения программ в Python. В ходе работы необходимо на практике разобрать отличия между многопоточностью (threading), многопроцессорностью (multiprocessing) и асинхронностью (asyncio), а также оценить их эффективность при решении вычислительных задач (CPU-bound) и задач, связанных с сетевым ожиданием (I/O-bound).
2. Задача 1: Расчет суммы чисел (CPU-bound задача)
В данной задаче производился расчет суммы чисел от 1 до 1 000 000 000. Для обеспечения параллелизма задача была разделена на части в соответствии с количеством доступных ядер процессора (2 ядра в среде Colab).
Результаты выполнения:
| Метод | Время (сек) | Особенности |
|---|---|---|
| Threading | 24.3171 | Ограничен GIL, эффективен только для I/O |
| Multiprocessing | 26.4207 | Обходит GIL, использует все ядра CPU |
| AsyncIO | 24.6560 | Один поток, эффективен для ожидания сети |
Анализ результатов:
При выполнении чисто вычислительной задачи (CPU-bound) методы Threading и AsyncIO показали практически идентичное время. Это объясняется работой GIL (Global Interpreter Lock), который не позволяет выполнять Python-код в нескольких потоках одновременно на разных ядрах. Multiprocessing в данном тесте оказался медленнее из-за значительных накладных расходов на создание отдельных процессов и копирование данных в памяти. Эксперимент подтвердил, что на коротких или средних дистанциях вычислений в Python накладные расходы процессов могут превышать выгоду от их использования.
3. Задача 2: Параллельный парсинг веб-страниц (I/O-bound задача)
Для тестирования сетевой нагрузки было выполнено 1000 запросов к тестовой странице https://qutoq.site/test/index.html. Программа извлекала заголовок страницы (<title>) и первый абзац текста, сохраняя данные в базу данных SQLite.
Результаты выполнения:
| Метод | Время выполнения (сек) |
|---|---|
| AsyncIO | 11.73 |
| Threading | 16.15 |
| Multiprocessing | 216.64 |
Проверка содержимого БД (фрагмент):
Для демонстрации корректности работы был выполнен проверочный запрос к базе данных lab_web.db:
ID | Title | Content
--------------------------------------------------------------------------------
1 | Lab 2: Performance Test Page | This is the test content we are saving into the SQ...
2 | Lab 2: Performance Test Page | This is the test content we are saving into the SQ...
3 | Lab 2: Performance Test Page | This is the test content we are saving into the SQ...
Анализ результатов:
В сетевых задачах (I/O-bound) ситуация кардинально изменилась: 1. AsyncIO стал лидером, так как механизм Event Loop позволяет эффективно переключаться между задачами в момент ожидания ответа от сервера, не создавая лишних системных ресурсов. 2. Threading показал отличный результат, так как потоки успешно "ждали" сетевой ответ параллельно. 3. Multiprocessing показал худший результат (в 18 раз медленнее AsyncIO). Это связано с тем, что количество одновременных запросов было жестко ограничено числом ядер процессора (2), из-за чего выполнение 1000 запросов стало практически последовательным.
4. Сравнительный анализ подходов
На основе проведенных тестов можно выделить ключевые особенности каждого подхода:
-
Threading (Потоки):
- Использует общую память процесса.
- Бесполезен для вычислений из-за GIL.
- Отлично подходит для простых I/O задач, но при очень большом количестве потоков (тысячи) начинает потреблять много памяти.
-
Multiprocessing (Процессы):
- У каждого процесса своя память и свой GIL.
- Единственный способ задействовать все ядра CPU для вычислений.
- Имеет высокий "порог входа": создание процесса — дорогая операция. Совершенно не подходит для задач с большим количеством мелких сетевых запросов.
-
AsyncIO (Асинхронность):
- Работает в одном потоке, используя кооперативную многозадачность.
- Требует использования специальных неблокирующих библиотек (
aiohttp,aiosqlite). - Самый эффективный метод для веб-парсинга и высоконагруженных сетевых сервисов.
5. Выводы
В результате выполнения лабораторной работы были выявлены сильные и слабые стороны механизмов конкурентности в Python. Экспериментально подтверждено, что для вычислительных задач необходимо использовать multiprocessing (при условии, что объем вычислений оправдывает затраты на запуск процессов), в то время как для сетевых задач и взаимодействия с базами данных наиболее эффективным и производительным решением является асинхронный подход (asyncio). Также в ходе работы были освоены навыки синхронизации данных (Lock) при конкурентной записи в SQLite.