> Flutter — Dart однопоточный или многопоточный язык (Flutter)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: ООО "ШВЕЦОВ", BSL

Стек: Flutter

> Пример ответа

Короткий ответ

Dart - однопоточный язык с моделью изолятов (isolates). Каждый isolate имеет собственную память и свой event loop, поэтому параллелизм достигается через запуск нескольких изолятов, а не через общие потоки. Внутри одного isolate код выполняется последовательно, но асинхронность обеспечивается через event loop и Future/Stream. Для Flutter это означает, что UI-поток не блокируется, а тяжёлые вычисления выносятся в отдельные изоляты.

Подробное объяснение

Dart использует модель однопоточного исполнения внутри каждого isolate. Это принципиально отличается от многопоточных языков вроде Java или C++, где потоки разделяют память и синхронизируются через locks. В Dart:

  • Isolate - это отдельная единица исполнения с собственной heap-памятью. Изоляты не разделяют состояние, общение происходит через порты (SendPort/ReceivePort) с помощью сообщений.
  • Event loop - внутри каждого isolate работает цикл событий, который обрабатывает microtasks и events из event queue. Это даёт асинхронность без блокировки.
  • Single-threaded execution - в конкретный момент времени выполняется только один код внутри isolate. Нет гонок данных и необходимости в мьютексах.
  • Isolate.spawn() - создаёт новый изолят, который работает параллельно на другом ядре CPU. Это даёт true parallelism, но с оверхедом на копирование данных при передаче сообщений.

Для Flutter это критично: UI-поток (main isolate) обрабатывает рендеринг и пользовательские события. Если выполнить тяжёлую синхронную операцию - интерфейс замрёт. Поэтому тяжёлые вычисления (JSON-парсинг, шифрование, обработка изображений) выносятся в compute() или Isolate.run().

На практике

В реальной Flutter-разработке:

  • Асинхронность - используйте async/await для I/O: сетевые запросы, чтение файлов, базы данных. Это не блокирует UI.
  • Тяжёлые вычисления - для задач, занимающих больше ~16 мс (один кадр), используйте Isolate.run() или compute(). Например, обработка большого списка, парсинг большого JSON.
  • Изоляты для фоновой работы - если нужно постоянно выполнять работу в фоне (например, обработка аудио), создайте долгоживущий isolate с портами.
  • Не используйте Future для CPU-bound задач - это не освободит main isolate, так как код всё равно выполнится в том же потоке.
  • Помните про передачу данных - при передаче в изолят данные копируются (или передаются по ссылке, если это immutable). Для больших объектов это может быть дорого.

Пример кода

import 'dart:isolate';

// Тяжёлая функция - выполняется в отдельном изоляте
int calculateSum(List<int> numbers) {
  return numbers.fold(0, (sum, n) => sum + n);
}

// Использование Isolate.run (Dart 2.19+)
Future<int> sumInBackground(List<int> numbers) async {
  return await Isolate.run(() => calculateSum(numbers));
}

// Альтернатива через compute в Flutter
// import 'package:flutter/foundation.dart';
// Future<int> sumWithCompute(List<int> numbers) {
//   return compute(calculateSum, numbers);
// }

// Пример с портами для долгоживущего изолята
void isolateWorker(SendPort sendPort) {
  ReceivePort receivePort = ReceivePort();
  sendPort.send(receivePort.sendPort);

  receivePort.listen((message) {
    if (message is List<int>) {
      sendPort.send(calculateSum(message));
    }
  });
}

void main() async {
  // Простой случай
  final result = await sumInBackground(List.generate(1000000, (i) => i));
  print('Sum: $result');

  // Долгоживущий изолят
  final receivePort = ReceivePort();
  await Isolate.spawn(isolateWorker, receivePort.sendPort);
  final workerSendPort = await receivePort.first as SendPort;

  final responsePort = ReceivePort();
  workerSendPort.send(List.generate(100, (i) => i));
  final sum = await responsePort.first;
  print('Worker sum: $sum');
}

Как отвечать на собеседовании

Начните с чёткого тезиса: Dart однопоточный, но с асинхронной моделью и изолятами. Затем объясните разницу между event loop и многопоточностью. Подчеркните, что изоляты - это не потоки, а отдельные сущности с собственной памятью. Приведите пример из практики: почему нельзя делать тяжёлые вычисления в main isolate и как это решается. Если спросят про Future - уточните, что это не параллелизм, а асинхронность. Хорошо показать понимание trade-off: изоляты безопаснее (нет гонок), но дороже по памяти и времени на передачу данных.

Что проверяет интервьюер

  • Понимание модели исполнения Dart, а не заученных фактов.
  • Умение различать асинхронность и параллелизм.
  • Знание, когда использовать изоляты, а когда достаточно async/await.
  • Понимание ограничений: Future не решает проблему блокировки UI.
  • Осведомлённость о Isolate.run(), compute(), портах и обмене сообщениями.
  • Практический опыт: как избегать jank в Flutter-приложениях.

Типичные ошибки

  • Утверждение, что Dart многопоточный из-за async/await - это асинхронность, а не многопоточность.
  • Использование Future для CPU-bound задач - код всё равно выполнится в том же потоке и заблокирует UI.
  • Забывают, что изоляты не разделяют память - попытка передать изменяемый объект приведёт к копированию или ошибке.
  • Создание изолята на каждую мелкую операцию - оверхед на spawn и передачу данных может превысить выгоду.
  • Непонимание, что compute() в Flutter - это обёртка над Isolate.run() с ограничением на один аргумент.
  • Путаница между microtask queue и event queue в event loop.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью