Dart跨Isolate共享数据:shared_map把150行管道代码砍成几行
AIAI Summary (BLUF)
Dart的Isolate采用无共享内存模型,跨Isolate共享数据通常需要繁琐的序列化或消息传递。shared_map包提供了一种优雅的解决方案,让你像操作普通Map一样在多个Isolate间同步读写数据。本文通过完整示例演示其核心用法,包括SharedMap、SharedMapCached和SharedStore。
核心洞察
Dart 的 isolate 不共享内存,这个设计消灭了数据竞争,但也让跨 isolate 共享缓存变成了一件麻烦事。shared_map 这个包用一套引用传递机制把端口通信的脏活全包了,代码量能砍掉一大半。我唯一担心的是它的性能边界,高频读写场景下跨 isolate 消息派发的开销到底有多大,文章里没给 benchmark,你自己压测一下比较稳妥。
这是 Dart 和 Flutter 系列的第三篇,每篇独立成文,不用担心没看过前两篇。
Dart 的并发模型建立在 Isolate 之上。和 Java、C++、Go 里的线程不同,Dart 的 isolate 之间不共享内存。每个 isolate 有自己的私有堆,也有自己的单线程事件循环。
这种不共享的设计很聪明。数据竞争、死锁、互斥锁争用、线程同步的坑,统统不存在。
但问题来了,你总会有需要跨 isolate 共享数据的时候。
想象一个常见的生产场景:
你在做一个 Flutter 应用,需要在后台跑重活,比如批量压缩图片、解析巨大的 JSON、算加密哈希、跑复杂的机器学习计算。为了让 UI 稳定在 120 帧,你用 Isolate.run 把这些活丢给后台 isolate。
现在假设这些并发后台任务都需要访问一个共享的内存缓存,比如已解析的元数据、认证 token、或者共享的计算结果,用来避免重复劳动。
在 Dart 里你怎么做?
以前你只有两个不太好的选择:
- 每次跨 isolate 边界都把整个数据结构序列化再拷贝一份。数据量大或者操作频繁的话,CPU 烧得厉害,GC 压力也大。
- 自己用
ReceivePort和SendPort搭一个消息传递服务器。你得自己设计请求响应的 DTO,生成唯一的请求关联 ID,接上响应 completer,写 150 行脆弱的管道代码,就为了做一次简单的键值查询。
还有第三个更好的选择,但几乎没人提:package:shared_map。
核心结论
shared_map是一个零依赖的纯 Dart 包,在 pub.dev 上获得 160/160 满分评分,完全兼容 Dart 3,支持 iOS、Android、macOS、Windows、Linux、Web 及服务端等所有 Dart 可运行平台。该包通过"引用模式"实现跨 isolate 数据共享:主 isolate 创建
SharedMap作为权威数据源,调用.sharedReference()生成轻量可序列化令牌,工作 isolate 通过SharedMap.fromSharedReference(ref)重建代理实例,所有读写操作自动派发回主实例并同步到所有 isolate。使用
shared_map可完全避免手写SendPort、ReceivePort、Completer及序列化样板代码,官方示例中两个后台 isolate 的跨 isolate 读写与同步仅需数行代码即可完成。shared_map内置SharedMapCached子类,可在工作 isolate 本地堆缓存已查询的键值,在超时窗口内(如 30 秒)的后续读取直接本地返回,避免高频读取场景下每次get()都触发跨 isolate 消息派发。shared_map适用于 CPU 密集型任务协调、内存去重与缓存、跨 isolate 指标计数等场景;但不适用于持久化磁盘存储(应使用 SQLite、Drift 等)和单 isolate 应用(普通Map<K, V>即可满足需求)。
shared_map 是什么
这个包由资深 Dart 工程师 Graciliano M. Passos 开发,提供了一个同步的 Map 数据结构,专门用来跨 Dart isolateDart的并发单元,拥有独立内存堆和单线程事件循环,Isolate之间不共享内存,通过消息传递通信。 和异步工作流共享数据。
几个让它值得关注的点:
- 零依赖:纯 Dart,没有任何第三方依赖。
- Pub 评分 160/160:pub.dev 上满分,完全兼容 Dart 3。
- 全平台支持:Dart 能跑的地方它就能跑,iOS、Android、macOS、Windows、Linux、Web、后端 CLI 和服务端都行。
- 熟悉的 Map 语义:用标准的异步键值方法操作,
get()、put()、putIfAbsent()、update()这些。
你不需要手动管理端口,shared_map 在底层透明地处理跨 isolate 通信协议。
工作原理:引用模式
shared_map 的核心心智模型很简单:
- 主实例:在主 isolate 上创建一个
SharedMap,比如 Flutter UI 线程或者服务端主循环。这个实例是权威数据源。 - 共享引用:调用
.sharedReference()生成一个轻量、可序列化的令牌。 - 辅助实例:把这个轻量令牌传过 isolate 边界,比如传进
Isolate.run。在 isolate 内部用SharedMap.fromSharedReference(ref)重建一个代理实例。
工作 isolate 做的任何读取、写入、修改都会自动派发回主实例,并同步到所有 isolate。
实际跑一遍
写一个完整的独立示例。模拟多个并发工作 isolate 处理数据,从共享缓存读取,顺便往缓存里写新条目:
import 'dart:isolate';
import 'package:shared_map/shared_map.dart';
void main() async {
// 1. 在主 isolate 上创建 SharedStore 和 SharedMap
final store = SharedStore('app_cache');
final userCache = await store.getSharedMap<String, String>('users');
// 预置一个初始值
await userCache!.put('user_101', 'Randal (Admin)');
// 2. 提取轻量、可序列化的引用
final cacheReference = userCache.sharedReference();
print('--- 启动后台 Worker 1 ---');
// 3. 把引用传进后台 isolate
final worker1Result = await Isolate.run(() async {
// 重建同步 map 代理
final workerMap = SharedMap<String, String>.fromSharedReference(cacheReference);
// 读取主 isolate 之前存的值
final user = await workerMap.get('user_101');
print('[Worker 1] 从共享缓存读取: $user');
// 从后台 worker 往共享缓存写入新值
await workerMap.put('user_102', 'Wilhelm (Engineer)');
return 'Worker 1 完成';
});
print(worker1Result);
print('--- 启动后台 Worker 2 ---');
// 4. 启动第二个 isolate,验证跨 isolate 同步
final worker2Result = await Isolate.run(() async {
final workerMap = SharedMap<String, String>.fromSharedReference(cacheReference);
// Worker 2 能立刻读到 Worker 1 刚写入的值
final user102 = await workerMap.get('user_102');
print('[Worker 2] 读取 Worker 1 写入的值: $user102');
// 原子地使用 putIfAbsent
final user103 = await workerMap.putIfAbsent('user_103', 'Guest User');
return '[Worker 2] 添加了: $user103';
});
print(worker2Result);
// 5. 验证主 isolate 反映了所有更新
print('--- 回到主 Isolate ---');
print('缓存总条目数: ${await userCache.length()}');
print('主 isolate 上的 user_102: ${await userCache.get('user_102')}');
print('主 isolate 上的 user_103: ${await userCache.get('user_103')}');
}
控制台输出
--- 启动后台 Worker 1 ---
[Worker 1] 从共享缓存读取: Randal (Admin)
Worker 1 完成
--- 启动后台 Worker 2 ---
[Worker 2] 读取 Worker 1 写入的值: Wilhelm (Engineer)
[Worker 2] 添加了: Guest User
--- 回到主 Isolate ---
缓存总条目数: 3
主 isolate 上的 user_102: Wilhelm (Engineer)
主 isolate 上的 user_103: Guest User
注意刚才发生了什么:
- 两个独立的后台 isolate 互相通信,并把数据共享回了主线程。
- 一行
SendPort、ReceivePort、Completer或序列化样板代码都没写。
进阶能力:用 SharedMapCached 做本地读缓存
如果你的工作 isolate 要做几千次快速读取,你可能不希望每次 get() 都走一次跨 isolate 消息派发。
shared_map 内置了一个子类叫 SharedMapCached:
final cachedWorkerMap = SharedMapCached<String, String>.fromSharedReference(
cacheReference,
// 在本 isolate 本地缓存条目,用于高吞吐读取
timeout: const Duration(seconds: 30),
);
用 SharedMapCached 查询已存在的键时,它会把值缓存在工作 isolate 的堆里。如果后续读取发生在超时窗口内,直接本地返回,没有跨 isolate 延迟。
用 SharedStore 分组管理 Map
复杂应用里你很少只有一个缓存。可能有:
- 图片缓存(
SharedMap<String, Uint8List>) - 用户资料缓存(
SharedMap<String, UserProfile>) - 限流器 map(
SharedMap<String, int>)
与其到处传几十个独立的引用,不如传一个 SharedStoreReference:
// 主线程:
final store = SharedStore('global_store');
await store.getSharedMap<String, String>('tokens');
await store.getSharedMap<String, int>('rate_limits');
final storeRef = store.sharedReference();
// 在任意后台 isolate 里:
await Isolate.run(() async {
final workerStore = SharedStore.fromSharedReference(storeRef);
// 动态解析这个 store 下注册的任意 map
final tokens = await workerStore.getSharedMap<String, String>('tokens');
final rateLimits = await workerStore.getSharedMap<String, int>('rate_limits');
// ...
});
什么时候该用,什么时候不该用
任何工具都有它的适用边界。
适合用 shared_map 的场景:
- CPU 密集型任务的协调:跑一批后台 isolate(
Isolate.run或常驻 worker isolate),需要共享查询。 - 内存去重和缓存:防止并发 worker 重复计算或下载同一个资源。
- 跨 isolate 指标和计数器:收集统计、遥测数据,或者跨线程的限流令牌。
不适合用 shared_map 的场景:
- 持久化磁盘存储:
shared_map是内存数据结构。数据需要跨应用重启存活的话,用 SQLite、Drift 或者持久化键值存储。 - 单 isolate 应用:所有代码都跑在根 UI isolate 上的话,一个普通的 Dart
Map<K, V>或者响应式 Signal 就够了,没必要为异步抽象付出代价。
总结
Dart 的 isolate 架构让我们的代码远离并发 bug,但你不该为了跨 worker 任务共享一个内存缓存就写几百行端口管道代码。
引入 shared_map 之后:
- Isolate 保持隔离,UI 线程保持响应。
- 不用再写
SendPort和ReceivePort的意大利面条代码。 - 得到原子、同步的键值存储,零外部依赖。
在 pubspec.yaml 里加上 shared_map: ^1.1.9,别再从头造 isolate 消息传递的轮子了。
Randal L. Schwartz 是 Dart 和 Flutter 的 Google Developer Expert(GDE),资深软件架构师。
- 在 YouTube 看深度讲解和实战演示:@RandalOnDartAndFlutter
- 在 GitHub 上联系:@RandalSchwartz
常见问题(FAQ)
shared_map一个纯Dart包,提供可在多个Isolate间共享的同步Map数据结构,零依赖,支持所有Dart平台。 和普通 Map 有什么区别?
shared_map 提供同步的 Map 数据结构,专为跨 Dart isolate 共享数据设计。它通过引用传递机制透明处理端口通信,让你像操作普通 Map 一样使用 get、put 等方法,无需手动序列化或管理 SendPort/ReceivePort。
shared_map 的性能怎么样?高频读写场景下会不会很慢?
shared_map 在高频读写时可能因跨 isolate 消息派发产生开销。文章未提供 benchmark,建议自行压测。对于高吞吐读取,可使用 SharedMapCachedSharedMap的子类,支持本地读取缓存,在指定超时时间内重复读取同一键时无需跨Isolate通信。 在本地缓存条目,减少跨 isolate 通信次数。
什么时候该用 shared_map?什么时候不该用?
适合需要跨 isolate 共享缓存、避免重复计算的场景,如后台任务共享元数据或认证 token。不适合对性能极度敏感的高频读写,或数据量极大且可容忍序列化成本的场景。建议先评估性能边界。
版权与免责声明:本文仅用于信息分享与交流,不构成任何形式的法律、投资、医疗或其他专业建议,也不构成对任何结果的承诺或保证。
文中提及的商标、品牌、Logo、产品名称及相关图片/素材,其权利归各自合法权利人所有。本站内容可能基于公开资料整理,亦可能使用 AI 辅助生成或润色;我们尽力确保准确与合规,但不保证完整性、时效性与适用性,请读者自行甄别并以官方信息为准。
若本文内容或素材涉嫌侵权、隐私不当或存在错误,请相关权利人/当事人联系本站,我们将及时核实并采取删除、修正或下架等处理措施。也请勿在评论或联系信息中提交身份证号、手机号、住址等个人敏感信息。



