synchronized
synchronized是一个优秀的锁定机制,用于防止并发访问异步代码。
目标是提出一种类似于临界区(criminal sections)的解决方案,并提供一个类似 Java 风格的简单 synchronized API。它提供了一个基本的锁/互斥锁解决方案,以支持事务等功能。
synchronized的工作原理就是下一个任务会在前一个任务完成后执行。
没有加锁的异步任务
Future<void> writeSlow(int value) async {
await Future<void>.delayed(const Duration(milliseconds: 1));
stdout.write(value);
}
Future<void> write(List<int> values) async {
for (var value in values) {
await writeSlow(value);
}
}
上面两个函数,write函数会循环遍历调用writeSlow函数并同步等待writeSlow任务完成。writeSlow在控制台简单打印日志。
Future<void> write1234() async {
await write([1, 2, 3, 4]);
}
write1234函数传入具体任务,执行后控制台会打印日志1234。
Future<void> test1() async {
stdout.writeln('not synchronized');
//await Future.wait([write1234(), write1234()]);
// ignore: unawaited_futures
write1234();
// ignore: unawaited_futures
write1234();
await Future<void>.delayed(const Duration(milliseconds: 50));
stdout.writeln();
}
test1函数内部会执行两次write1234函数。控制台会打印:
11223344
从打印日志看, 第二次的任务并没有等待第一次的任务全部执行完成后再执行,而是两次任务交替执行了。
加锁的异步任务
同样的多个任务, 加锁后就可以保证各个任务同步执行了。
Future<void> runSynchronized() async {
stdout.writeln('synchronized');
final lock = Lock();
// this should print 12341234
// ignore: unawaited_futures
lock.synchronized(write1234);
// ignore: unawaited_futures
lock.synchronized(write1234);
await Future<void>.delayed(const Duration(milliseconds: 50));
stdout.writeln();
}
runSynchronized函数内部用同步锁Lock控制两个任务write1234。执行后, 控制台会打印:
12341234。
这样第二次任务就会等待第一个任务执行完成后再执行。
Lock
/// Create a shared [Lock] object and use it using the [Lock.synchronized] /// 创建一个共享的 [Lock] 对象,并通过 [Lock.synchronized] /// method to prevent concurrent access to a shared resource. /// 方法来防止并发访问共享资源。
/// Object providing the implicit lock.
/// 提供隐式锁的对象。
///
/// A [Lock] can be reentrant (in this case it will use a [Zone]).
/// [Lock] 可以是可重入的(此时会使用 [Zone])。
///
/// non-reentrant lock is used like an aync executor with a capacity of 1.
/// 非重入锁的使用类似容量为 1 的异步执行器。
///
/// if [timeout] is not null, it will timeout after the specified duration.
/// 如果 [timeout] 不为 null,则会在指定持续时间后超时。
abstract class Lock {
/// Creates a [Lock] object.
/// 创建一个 [Lock] 对象。
///
/// if [reentrant], it uses [Zone] to allow inner [synchronized] calls.
/// 如果启用 [reentrant],则使用 [Zone] 以允许嵌套的 [synchronized] 调用。
factory Lock({bool reentrant = false}) {
if (reentrant) {
return ReentrantLock();
} else {
return BasicLock();
}
}
/// Executes [computation] when lock is available.
/// 当锁可用时执行 [computation]。
///
/// Only one asynchronous block can run while the lock is retained.
/// 在持有锁期间,只有一个异步代码块可以运行。
///
/// If [timeout] is specified, it will try to grab the lock and will not
/// 如果指定了 [timeout],它会尝试获取锁,并且在无法在给定时长内获取锁时,
/// call the computation callback and throw a [TimeoutExpection] if the lock
/// 不会调用计算回调,并会抛出 [TimeoutExpection]。
/// cannot be grabbed in the given duration.
/// 如果在给定时长内无法获取锁。
Future<T> synchronized<T>(
FutureOr<T> Function() computation, {
Duration? timeout,
});
/// returns true if the lock is currently locked.
/// 如果当前锁已被持有则返回 true。
bool get locked;
/// for reentrant, test whether we are currently in the synchronized section.
/// 对于可重入锁,测试当前是否处于 synchronized 区段内。
/// for non reentrant, it returns the [locked] status.
/// 对于非重入锁,它返回 [locked] 状态。
///
/// For non-reentrant lock, it matches the [locked] status and since it does
/// 对非重入锁,此值与 [locked] 状态一致;由于语义并不明确,
/// not mean anything, it should not be used as behavior may change in the future.
/// 不应依赖该值,未来行为可能会发生变化。
bool get inLock;
/// It returns true if the lock can be locked. For basic lock (reentrant or
/// 当锁可以被加锁时返回 true。对于基础锁(可重入或不可重入),
/// not), it is when the lock is not locked.
/// 当锁未被持有时为 true。
///
/// For multi lock, it is when any lock is locked.
/// 对于多锁(MultiLock),当任一锁被持有时,相应行为按实现定义。
bool get canLock;
}
BasicLock
BasicLock真正实现加锁机制, 加锁原理。
/// Basic (non-reentrant) lock
class BasicLock implements Lock {
/// The last running block
Future<dynamic>? last;
@override
bool get locked => last != null;
@override
bool get canLock => !locked;
@override
Future<T> synchronized<T>(FutureOr<T> Function() func, {
Duration? timeout,
}) async {
print('\n ');
var step = 0;
String addr(Object? o) => o == null ? 'null' : '${o.runtimeType}@${identityHashCode(o)}';
void log(String m) {
print('✅ $m(步骤=${++step})');
}
log('进入 synchronized:locked=$locked last=${addr(last)} timeout=$timeout func=$func');
log('赋值 prev=last 之前:last=${addr(last)}');
final prev = last;
log('赋值 prev=last 之后:prev=${addr(prev)}');
log('创建 completer 之前');
final completer = Completer<void>.sync();
log('创建 completer 之后:completer=${addr(completer)} completer.future=${addr(completer.future)}');
log('设置 last=completer.future 之前:last=${addr(last)}');
last = completer.future;
log('设置 last 之后:last=${addr(last)}');
try {
log('准备等待前一个任务(如需要):prev=${addr(prev)} timeout=$timeout');
// If there is a previous running block, wait for it
if (prev != null) {
log('prev 非空,开始等待');
if (timeout != null) {
log('调用 prev.timeout 前:prev=${addr(prev)} timeout=$timeout');
// This could throw a timeout error
await prev.timeout(timeout);
log('调用 prev.timeout 后');
} else {
log('开始 await prev');
await prev;
log('await prev 完成');
}
} else {
log('prev 为空,无需等待');
}
log('等待步骤完成');
log('调用 func() 之前');
// Run the function and return the result
var result = func();
log('调用 func() 之后:result=$result type=${result.runtimeType}');
if (result is Future) {
log('result 是 Future,开始 await');
final awaited = await result;
log('await result 完成:value=$awaited type=${awaited.runtimeType}');
log('返回异步结果');
print('\n ');
return awaited;
} else {
log('返回同步结果:value=$result type=${result.runtimeType}');
print('\n ');
return result;
}
} finally {
log('进入 finally 清理:last=${addr(last)} prev=${addr(prev)} timeout=$timeout');
// Cleanup
// waiting for the previous task to be done in case of timeout
void complete() {
log('complete() 开始:last=${addr(last)} completer=${addr(completer)} completer.future=${addr(completer.future)}');
// Only mark it unlocked when the last one complete
if (identical(last, completer.future)) {
log('last 与 completer.future 相同,设置 last=null');
last = null;
log('last 已变为 null');
} else {
log('last 与 completer.future 不同,保持 last=${addr(last)}');
}
log('调用 completer.complete() 之前');
completer.complete();
log('调用 completer.complete() 之后');
}
// In case of timeout, wait for the previous one to complete too
// before marking this task as complete
if (prev != null && timeout != null) {
log('prev 与 timeout 均非空,安排 prev.then 回调');
// But we still returns immediately
// ignore: unawaited_futures
prev.then((_) {
log('prev.then 回调开始');
complete();
log('prev.then 回调结束');
});
log('已安排 prev.then');
} else {
log('直接调用 complete()');
complete();
log('complete() 已返回');
}
log('退出 finally 清理');
}
}
@override
String toString() {
return 'Lock[${identityHashCode(this)}]';
}
@override
bool get inLock => locked;
}
特别重要原理:
BasicLock 的实现其实是基于 “Future 串链(chaining)” 模型:
t1 → t2 → t3 → ...
- 每一个
synchronized创建一个新的 Future(completer.future) - 把它接在 last 的后面
- 并等待上一个任务完成
整体流程:
-
A 调用 synchronized
- last=A
- A 执行
-
B 调用 synchronized
- last=B
- B 发现 prev=A → 等待 A 完成
-
A 结束 → complete()
- last=null(如果当前任务就是 last)
-
B 开始执行
典型的“互斥锁(mutex)串行执行”效果。
last
/// The last running block
Future<dynamic>? last;
记录最后一个执行中的 Future。
last 是一个 Future,代表“当前或最后执行的任务”。
只要 last != null,说明锁被占用,新的任务必须等待它完成。
locked / canLock
@override
bool get locked => last != null;
@override
bool get canLock => !locked;
-
locked: 是否被锁住 -
canLock: 是否可以获取锁
synchronized 方法(核心)
final prev = last;
final completer = Completer<void>.sync();
last = completer.future;
-
prev:保存上一个执行任务 -
completer:创建一个新的任务句柄 -
将
last更新为这个新任务的 future
等待上一个任务完成
if (prev != null) {
if (timeout != null) {
await prev.timeout(timeout);
} else {
await prev;
}
}
若有上一个任务,则等待它完成;若设置了 timeout 则使用超时等待。
锁本质是:
只要 previous 没完成(complete),当前任务就不能执行。
执行 func()
var result = func();
if (result is Future) {
return await result;
} else {
return result;
}
执行用户提供的函数,若其返回 Future 则等待,否则直接返回。
finally(释放锁)
void complete() {
if (identical(last, completer.future)) {
last = null;
}
completer.complete();
}
-
若当前任务仍然是 last,则将 last 设为 null(锁释放)
-
完成当前任务 future(通知下一个等待者)
这里保证:
- 锁只会在当前任务完成时释放
- 后续任务看到
prev完成后即可执行
特殊处理:超时时
if (prev != null && timeout != null) {
prev.then((_) {
complete();
});
} else {
complete();
}
如果你设置了 timeout:
- 当前任务会 立即返回 timeout 错误
- 但是锁不能提前释放
- 所以必须等待真正的
prev完成后才释放锁
🔥 总体工作原理(非常重要)
BasicLock 的实现其实是基于 “Future 串链(chaining)” 模型:
t1 → t2 → t3 → ...
- 每一个 synchronized 创建一个新的 Future(completer.future)
- 把它接在 last 的后面
- 并等待上一个任务完成
runSynchronized同步执行函数
Future<void> runSynchronized() async {
stdout.writeln('synchronized');
final lock = Lock();
// this should print 12341234
// ignore: unawaited_futures
lock.synchronized(write1234);
// ignore: unawaited_futures
lock.synchronized(write5678);
lock.synchronized(write9101112);
await Future<void>.delayed(const Duration(milliseconds: 50));
stdout.writeln();
}
Future<void> write1234() async {
await write([1, 2, 3, 4]);
}
Future<void> write5678() async {
await write([5, 6, 7, 8]);
}
Future<void> write9101112() async {
await write([9, 10, 11, 12]);
}
日志如下:
synchronized
✅ 进入 synchronized:locked=false last=null timeout=null func=Closure: () => Future<void> from Function 'write1234': static.(步骤=1)
✅ 赋值 prev=last 之前:last=null(步骤=2)
✅ 赋值 prev=last 之后:prev=null(步骤=3)
✅ 创建 completer 之前(步骤=4)
✅ 创建 completer 之后:completer=_SyncCompleter<void>@971821951 completer.future=Future<void>@242937982(步骤=5)
✅ 设置 last=completer.future 之前:last=null(步骤=6)
✅ 设置 last 之后:last=Future<void>@242937982(步骤=7)
✅ 准备等待前一个任务(如需要):prev=null timeout=null(步骤=8)
✅ prev 为空,无需等待(步骤=9)
✅ 等待步骤完成(步骤=10)
✅ 调用 func() 之前(步骤=11)
✅ 调用 func() 之后:result=Instance of 'Future<void>' type=Future<void>(步骤=12)
✅ result 是 Future,开始 await(步骤=13)
✅ 进入 synchronized:locked=true last=Future<void>@242937982 timeout=null func=Closure: () => Future<void> from Function 'write5678': static.(步骤=1)
✅ 赋值 prev=last 之前:last=Future<void>@242937982(步骤=2)
✅ 赋值 prev=last 之后:prev=Future<void>@242937982(步骤=3)
✅ 创建 completer 之前(步骤=4)
✅ 创建 completer 之后:completer=_SyncCompleter<void>@919542402 completer.future=Future<void>@352620904(步骤=5)
✅ 设置 last=completer.future 之前:last=Future<void>@242937982(步骤=6)
✅ 设置 last 之后:last=Future<void>@352620904(步骤=7)
✅ 准备等待前一个任务(如需要):prev=Future<void>@242937982 timeout=null(步骤=8)
✅ prev 非空,开始等待(步骤=9)
✅ 开始 await prev(步骤=10)
✅ 进入 synchronized:locked=true last=Future<void>@352620904 timeout=null func=Closure: () => Future<void> from Function 'write9101112': static.(步骤=1)
✅ 赋值 prev=last 之前:last=Future<void>@352620904(步骤=2)
✅ 赋值 prev=last 之后:prev=Future<void>@352620904(步骤=3)
✅ 创建 completer 之前(步骤=4)
✅ 创建 completer 之后:completer=_SyncCompleter<void>@1072996005 completer.future=Future<void>@19405319(步骤=5)
✅ 设置 last=completer.future 之前:last=Future<void>@352620904(步骤=6)
✅ 设置 last 之后:last=Future<void>@19405319(步骤=7)
✅ 准备等待前一个任务(如需要):prev=Future<void>@352620904 timeout=null(步骤=8)
✅ prev 非空,开始等待(步骤=9)
✅ 开始 await prev(步骤=10)
1234✅ await result 完成:value=null type=Null(步骤=14)
✅ 返回异步结果(步骤=15)
✅ 进入 finally 清理:last=Future<void>@19405319 prev=null timeout=null(步骤=16)
✅ 直接调用 complete()(步骤=17)
✅ complete() 开始:last=Future<void>@19405319 completer=_SyncCompleter<void>@971821951 completer.future=Future<void>@242937982(步骤=18)
✅ last 与 completer.future 不同,保持 last=Future<void>@19405319(步骤=19)
✅ 调用 completer.complete() 之前(步骤=20)
✅ await prev 完成(步骤=11)
✅ 等待步骤完成(步骤=12)
✅ 调用 func() 之前(步骤=13)
✅ 调用 func() 之后:result=Instance of 'Future<void>' type=Future<void>(步骤=14)
✅ result 是 Future,开始 await(步骤=15)
✅ 调用 completer.complete() 之后(步骤=21)
✅ complete() 已返回(步骤=22)
✅ 退出 finally 清理(步骤=23)
5678✅ await result 完成:value=null type=Null(步骤=16)
✅ 返回异步结果(步骤=17)
✅ 进入 finally 清理:last=Future<void>@19405319 prev=Future<void>@242937982 timeout=null(步骤=18)
✅ 直接调用 complete()(步骤=19)
✅ complete() 开始:last=Future<void>@19405319 completer=_SyncCompleter<void>@919542402 completer.future=Future<void>@352620904(步骤=20)
✅ last 与 completer.future 不同,保持 last=Future<void>@19405319(步骤=21)
✅ 调用 completer.complete() 之前(步骤=22)
✅ await prev 完成(步骤=11)
✅ 等待步骤完成(步骤=12)
✅ 调用 func() 之前(步骤=13)
✅ 调用 func() 之后:result=Instance of 'Future<void>' type=Future<void>(步骤=14)
✅ result 是 Future,开始 await(步骤=15)
✅ 调用 completer.complete() 之后(步骤=23)
✅ complete() 已返回(步骤=24)
✅ 退出 finally 清理(步骤=25)
9101112✅ await result 完成:value=null type=Null(步骤=16)
✅ 返回异步结果(步骤=17)
✅ 进入 finally 清理:last=Future<void>@19405319 prev=Future<void>@352620904 timeout=null(步骤=18)
✅ 直接调用 complete()(步骤=19)
✅ complete() 开始:last=Future<void>@19405319 completer=_SyncCompleter<void>@1072996005 completer.future=Future<void>@19405319(步骤=20)
✅ last 与 completer.future 相同,设置 last=null(步骤=21)
✅ last 已变为 null(步骤=22)
✅ 调用 completer.complete() 之前(步骤=23)
✅ 调用 completer.complete() 之后(步骤=24)
✅ complete() 已返回(步骤=25)
✅ 退出 finally 清理(步骤=26)