Что такое многопоточность (multithreading) в Dart?

Ответ

В классическом понимании (как в Java или C++) Dart не является многопоточным языком. Он использует модель изолятов (Isolates).

Изолят — это независимая единица выполнения со своей собственной памятью (кучей) и циклом событий (event loop). Изоляты не разделяют память, что полностью исключает состояние гонки (race conditions) и необходимость в блокировках (locks). Общение между изолятами происходит только через передачу сообщений.

Зачем это нужно во Flutter? Чтобы выполнять тяжелые вычисления (обработка изображений, сложная сортировка больших массивов, криптография) без "подвисаний" UI, который работает в главном изоляте.

Пример использования Isolate для тяжелых вычислений:

import 'dart:isolate';

// Функция, которая будет выполняться в новом изоляте.
// Она получает SendPort главного изолята, чтобы отправить ему результат.
void _heavyComputationInIsolate(SendPort mainIsolateSendPort) {
  // Имитация долгой CPU-интенсивной задачи (например, вычисление числа Пи).
  double result = 0;
  for (int i = 0; i < 1000000000; i++) {
    result += (i % 2 == 0 ? 1 : -1) / (2 * i + 1);
  }
  result *= 4;

  // Отправляем результат обратно в главный изолят.
  mainIsolateSendPort.send(result);
}

// Главная функция (выполняется в UI-изоляте).
Future<double> calculatePiAsync() async {
  final receivePort = ReceivePort(); // Пор для получения сообщений.

  // Создаем новый изолят и передаем ему SendPort нашего receivePort.
  await Isolate.spawn(_heavyComputationInIsolate, receivePort.sendPort);

  // Ожидаем первое (и в данном случае единственное) сообщение от изолята.
  final result = await receivePort.first as double;

  receivePort.close(); // Важно закрыть порт.
  return result;
}

// Использование во Flutter виджете:
// void _startCalculation() async {
//   final pi = await calculatePiAsync();
//   setState(() { _result = pi; }); // Обновляем UI с результатом.
// }

Упрощенная альтернатива: compute() Во Flutter есть удобная функция-обертка compute (из package:flutter/foundation.dart), которая скрывает ручное управление изолятами для чистых функций.

import 'package:flutter/foundation.dart';

double _heavyFunction(int iterations) {
  // ... та же тяжелая логика ...
  return result;
}

void _startCompute() async {
  // Flutter сам создаст изолят, выполнит в нем _heavyFunction,
  // вернет результат и закроет изолят.
  final result = await compute(_heavyFunction, 1000000000);
  print(result);
}

Итог: Dart реализует параллелизм через изоляты (без общей памяти), что безопаснее классических потоков и идеально подходит для задач, где нужно не блокировать UI.

Ответ 18+ 🔞

Слушай, а вот тебе интересный момент про Дарт, который многих вводит в ступор. Все привыкли, что многопоточность — это как в Java: бери потоки, дели память, потом три ночи дебажишь гонки данных и думаешь, нахуя тебе эта жизнь нужна.

А Дарт — он хитрая жопа. Он говорит: «Нет, мужики, давайте по-другому». И вместо классических потоков у него есть изоляты (Isolates). Это, ёпта, как отдельные вселенные. У каждого изолята своя память, свой цикл событий, и они друг с другом не шастают туда-сюда. Никаких общих переменных, никаких мьютексов — доверия ебать ноль, поэтому и гонок данных не бывает в принципе. Общаются они только почтальонами — через передачу сообщений.

Зачем это во Flutter? Да всё просто! Твой UI живёт в главном изоляте. А если тебе надо, например, на клиенте гигабайтный JSON распарсить или изображение через фильтр прогнать, то главный изолят встанет колом, и интерфейс будет тормозить, как старая телега. Вот чтобы этого не было, ты эту тяжёлую хуйню скидываешь в отдельный изолят. Пусть там себе процессор палит, а UI остаётся отзывчивым.

Вот смотри, как это в коде выглядит:

import 'dart:isolate';

// Эта функция будет вариться в своём собственном мирке-изоляте.
void _heavyComputationInIsolate(SendPort mainIsolateSendPort) {
  // Допустим, тут какое-то ебанутое вычисление, типа числа Пи.
  double result = 0;
  for (int i = 0; i < 1000000000; i++) {
    result += (i % 2 == 0 ? 1 : -1) / (2 * i + 1);
  }
  result *= 4;

  // Готово? Кидаем результат обратно в главный поток, в окошко.
  mainIsolateSendPort.send(result);
}

// А это мы уже в UI-потоке вызываем.
Future<double> calculatePiAsync() async {
  final receivePort = ReceivePort(); // Делаем ящик для входящих писем.

  // Породим нового работничка-изолят и дадим ему адрес нашего ящика.
  await Isolate.spawn(_heavyComputationInIsolate, receivePort.sendPort);

  // Сидим, ждём первое (и единственное) письмо с результатом.
  final result = await receivePort.first as double;

  receivePort.close(); // Ящик закрыли, чтоб мусор не копился.
  return result;
}

// Ну и в виджете потом:
// void _startCalculation() async {
//   final pi = await calculatePiAsync(); // UI не виснет!
//   setState(() { _result = pi; }); // Обновили циферку на экране.
// }

Но есть же способ проще, чтоб мозг не выносило! Flutter, он же для людей, там есть готовая обёртка compute (из package:flutter/foundation.dart). Она сама создаст изолят, кинет туда твою функцию с аргументами, получит результат и всё приберёт. Красота!

import 'package:flutter/foundation.dart';

double _heavyFunction(int iterations) {
  // ... та же самая тяжёлая логика ...
  return result;
}

void _startCompute() async {
  // Одна строка, и никакой возни с портами! Flutter — молодец.
  final result = await compute(_heavyFunction, 1000000000);
  print(result);
}

Итог-то какой? Дарт реализует параллелизм через изоляты — безопасно, без общей памяти. Это не классические потоки, но для большинства задач в мобилке — то, что доктор прописал. UI не виснет, данные не corrupt'ятся, нервы целы.

Видео-ответы