<===

ProNotes

2025-10-25 09:36:17
# Управление памятью в MicroPython с `gc` и `asyncio`: Простое применение

Встраиваемые системы, такие как микроконтроллеры на MicroPython, имеют ограниченный объем оперативной памяти (RAM), часто всего десятки килобайт. В асинхронных программах с использованием `asyncio` (например, для IoT-устройств с Wi-Fi или датчиками) управление памятью становится критически важным, чтобы избежать утечек и фрагментации. Модуль `gc` (garbage collector) в MicroPython помогает эффективно управлять памятью, освобождая неиспользуемые объекты и предотвращая сбои. Эта статья объясняет, как использовать `gc` в связке с `asyncio`, с простыми примерами, включая управление Wi-Fi точкой доступа, как в реальных IoT-приложениях.

---

## Зачем нужен `gc` в MicroPython?

MicroPython автоматически управляет памятью через сборщик мусора, но в асинхронных программах с `asyncio` (например, при работе с датчиками, сетью или длительными циклами) создаются временные объекты (строки, списки, корутины), которые могут не сразу освобождаться. Это приводит к:

- **Утечкам памяти**: Объекты, удерживаемые ссылками, не очищаются.
- **Фрагментации**: Память разбивается на мелкие блоки, что снижает доступный объем.
- **Сбоям**: Микроконтроллер зависает при исчерпании RAM.

Модуль `gc` позволяет вручную запускать сборку мусора, отслеживать использование памяти и настраивать автоматическую очистку, что особенно важно в `asyncio`, где корутины усложняют управление памятью.

---

## Основы использования `gc`

Модуль `gc` предоставляет следующие ключевые функции:

- **`gc.enable()`**: Включает автоматическую сборку мусора (по умолчанию включена).
- **`gc.disable()`**: Отключает автоматический GC (используйте осторожно, только для оптимизации).
- **`gc.collect()`**: Принудительно запускает сборку мусора, освобождая память.
- **`gc.mem_free()`**: Возвращает объем свободной памяти (в байтах).
- **`gc.mem_alloc()`**: Возвращает объем занятой памяти.
- **`gc.threshold(amount)`**: Устанавливает порог, при котором GC автоматически запускается.

Для асинхронных программ важно вызывать `gc.collect()` в правильных местах, чтобы не блокировать цикл событий `asyncio`, и мониторить память для диагностики.

---

## Простое применение `gc` с `asyncio`

Рассмотрим пример программы для микроконтроллера, которая создает Wi-Fi точку доступа (AP) с динамическим SSID на основе показаний аналогового датчика (как в вашем коде). Мы добавим управление памятью с помощью `gc` и интегрируем его с `asyncio` для асинхронного выполнения.

### Пример кода: Wi-Fi AP с управлением памятью

```python
import gc
import asyncio
import wifi
import board
import analogio

# Настройки Wi-Fi
SSID_PREFIX = "sensor1:"
PASSWORD = "*******"

# Инициализация аналогового входа
analog_in = analogio.AnalogIn(board.A0)

async def gc_task():
    """Фоновая задача для периодической сборки мусора"""
    while True:
        gc.collect()  # Очистка памяти
        gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())  # Порог: ~25% занятой памяти
        print(f"Memory - Free: {gc.mem_free()} bytes, Allocated: {gc.mem_alloc()} bytes")
        await asyncio.sleep(10)  # Запуск каждые 10 секунд

async def wifi_task():
    """Задача для управления Wi-Fi точкой доступа"""
    while True:
        # Чтение напряжения с датчика
        voltage = (analog_in.value / 65535) * 3.3
        ssid = f"{SSID_PREFIX}{voltage:.4f}"[:32]  # Ограничение длины SSID
        wifi.radio.stop_ap()  # Остановка текущей точки доступа
        wifi.radio.start_ap(ssid=ssid, password=PASSWORD)  # Запуск новой
        gc.collect()  # Очистка после Wi-Fi операций
        print(f"AP: SSID={ssid}, IP={wifi.radio.ipv4_address_ap or 'Not assigned'}")
        await asyncio.sleep(15)  # Пауза 15 секунд

async def main():
    """Основная корутина"""
    gc.enable()  # Убедимся, что GC включен
    # Запускаем задачи
    asyncio.create_task(gc_task())
    asyncio.create_task(wifi_task())
    # Ждем бесконечно
    await asyncio.sleep(3600)

# Запуск программы
asyncio.run(main())
```

### Как это работает

1. **Фоновая задача GC (`gc_task`)**:
   - Вызывает `gc.collect()` каждые 10 секунд, чтобы освободить память.
   - Настраивает `gc.threshold()` для автоматического запуска GC, когда занято ~25% свободной памяти.
   - Выводит статистику памяти для мониторинга.

2. **Wi-Fi задача (`wifi_task`)**:
   - Читает данные с аналогового входа (`board.A0`) и формирует динамический SSID.
   - Управляет Wi-Fi точкой доступа (`start_ap`, `stop_ap`).
   - Вызывает `gc.collect()` после операций с Wi-Fi, чтобы минимизировать фрагментацию.

3. **Главная корутина (`main`)**:
   - Запускает обе задачи асинхронно и продолжает работать, не блокируя цикл событий.

---

## Нюансы использования `gc` с `asyncio`

1. **Блокировка цикла событий**:
   - `gc.collect()` блокирует выполнение на 10–50 мс. В `asyncio` это может нарушить тайминг критических задач (например, чтение датчиков или отправка данных).
   - **Решение**: Выполняйте `gc.collect()` в фоновой задаче с `await asyncio.sleep()`, чтобы не мешать другим корутинам.

2. **Создание объектов**:
   - Асинхронные программы часто создают временные объекты (строки, списки), которые удерживаются в памяти из-за ссылок в цикле событий.
   - **Решение**: Минимизируйте создание объектов. Например, используйте `bytearray` для строк:
     ```python
     ssid_buffer = bytearray(32)  # Фиксированный буфер
     ssid = f"sensor1:{voltage:.4f}"[:32]
     ssid_buffer[:len(ssid)] = ssid.encode()  # Переиспользование
     ```

3. **Мониторинг памяти**:
   - Постоянное снижение `gc.mem_free()` указывает на утечки (например, незавершенные корутины или глобальные списки).
   - **Решение**: Периодически проверяйте `gc.mem_free()` и `len(asyncio.all_tasks())` для диагностики.

4. **Настройка порога**:
   - Без `gc.threshold()` автоматический GC срабатывает только при исчерпании памяти, что может быть поздно.
   - **Решение**: Установите порог, как в примере: `gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())`.

---

## Практические советы

- **Тестируйте долго**: Запустите программу на 24+ часа и следите за `gc.mem_free()`. Если память падает, проверьте, не накапливаются ли задачи (`asyncio.all_tasks()`).
- **Оптимизируйте Wi-Fi**: Операции вроде `start_ap` создают временные буферы. Вызывайте `gc.collect()` после них.
- **Диагностика**: Если поддерживается, используйте `micropython.mem_info()` для детального анализа памяти.
- **Избегайте `gc.disable()`**: Отключение GC оправдано только для задач с микросекундным таймингом (например, GPIO). Всегда включайте обратно с `gc.enable()`.

---

## Почему это важно?

В IoT-приложениях, таких как ваш код с Wi-Fi точкой доступа, микроконтроллеры работают непрерывно, создавая множество временных объектов. Без управления памятью через `gc` устройство может исчерпать RAM за часы или дни, особенно в асинхронных программах с `asyncio`. Простое добавление `gc_task` и вызовов `gc.collect()` после "тяжелых" операций (Wi-Fi, строки) может увеличить стабильность на порядки, предотвращая сбои.

---

## Заключение

Модуль `gc` в MicroPython — мощный инструмент для управления памятью, особенно в асинхронных программах с `asyncio`. Используйте фоновые задачи для периодической сборки мусора, настраивайте порог с `gc.threshold()`, минимизируйте создание объектов и мониторьте память. Пример выше — это готовый шаблон для IoT-приложений, который можно адаптировать под датчики, сеть или другие задачи. Если вы хотите расширить код (например, добавить логирование или обработку ошибок), просто добавьте детали, и я помогу доработать!
← Previous Next →
Back to list