跪拜 Guibai
← All articles
Frontend · Flutter · Android

Dart's Shared-Memory Multithreading Lands with IsolateGroupBound

By 恋猫de小郭 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

This removes a fundamental interop tax for Flutter apps that embed native audio engines, UI frameworks, or other C/C++ libraries. Native threads can now call Dart synchronously without the overhead of copying data or the complexity of routing through a specific isolate's event loop.

Summary

Dart's long-running effort to add shared memory to isolates has reached a concrete milestone. The `NativeCallable.isolateGroupBound` API, now in Dart 3.12.2, allows native code on any thread to synchronously execute a Dart callback within an isolate group, without binding to a particular isolate. This callback can only access static or top-level state explicitly marked as shared via `@pragma('vm:shared')`.

Shared state is currently limited to deeply immutable types, `TypedData`, `Struct`, and closures meeting strict capture constraints. Ordinary mutable Dart objects like `List` and `Map` remain isolate-local. The new `dart:concurrent` library provides `Mutex` and `ConditionVariable` to manage concurrent access to shared mutable binary data, since the VM does not guarantee sequential consistency for those writes.

The design solves two persistent pain points: the high cost of copying large object graphs between isolates, and the friction of native threads needing to understand Dart's isolate model. A future goal is for Dart AOT code to behave like a standard C library, where any native thread can call exported Dart symbols directly. The current implementation uses a hybrid of compile-time and runtime checks, and `dart:async` APIs like `Future` and `Stream` are unavailable in this context, making it best suited for short, synchronous FFI callbacks.

Takeaways
`NativeCallable.isolateGroupBound` lets any native thread synchronously execute a Dart callback within the creator's isolate group, but outside any specific isolate.
Code running in this context can only access static or top-level state marked with `@pragma('vm:shared')`; accessing ordinary non-shared static state throws an `AccessError`.
Shareable types include strings, numbers, compile-time constants, `TypedData`, `Struct`, static method tear-offs, and closures with trivially shareable captures. Ordinary `List` and `Map` objects cannot be shared.
The `dart:concurrent` library now includes `Mutex` and `ConditionVariable` to prevent data races on shared mutable binary data like `Uint8List`.
`dart:async` APIs (`Future`, `Stream`, `Timer`, `Zone`) are unavailable in isolate-group-bound callbacks, limiting them to synchronous, short-lived FFI work.
Third-party packages that use ordinary static variables for caching or singletons will break inside these callbacks, and the resulting `AccessError` can be hard to trace.
A long-term goal is to let Dart AOT code be linked into native apps like a C library, with any thread calling `@ffi.Export()` symbols directly.
Conclusions

By restricting shared state to a narrow set of trivially shareable types instead of opening up arbitrary Dart objects, the Dart team sidesteps the hardest concurrency problems while still solving the most expensive interop bottlenecks.

The hybrid checking approach—compile-time for field declarations, creation-time for callback captures, and runtime for deep call chains—is a pragmatic compromise that avoids a massive type-system overhaul but leaves a class of bugs that only surface in production.

Third-party ecosystem compatibility is the biggest hidden risk. Libraries written under the old isolate model freely use static state, and none of them were designed for an execution context where that state is off-limits.

Making `dart:io` eventually implementable in pure Dart is a signal that the team sees this shared-memory model as foundational enough to rewrite core platform libraries on top of it.

Concepts & terms
Isolate Group
A set of Dart isolates that share code, type information, and potentially the same managed heap, but still enforce object access boundaries so isolates cannot directly read each other's ordinary mutable objects.
NativeCallable.isolateGroupBound
A Dart FFI API that creates a callable object allowing native code on any thread to synchronously invoke a Dart function within the isolate group of its creator, without entering a specific isolate.
@pragma('vm:shared')
An annotation marking a static field or top-level variable as shared across an isolate group. The field must be final and its value must be deeply immutable or trivially shareable.
Trivially Shareable
A constraint on Dart objects that can be safely shared between isolates without copying. Includes strings, numbers, compile-time constants, TypedData, Struct, and closures that only capture shareable values.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗