Mutexes in Dart
In the previous example we saw how to manage simple counter state using atomic operations. For more complex state we can use a mutex to safely access data across multiple isolates.
import 'dart:async';
import 'dart:isolate';
class Container {
final Map<String, int> _counters = {};
final _lock = Lock();
Future<void> inc(String name) async {
await _lock.synchronized(() {
_counters[name] = (_counters[name] ?? 0) + 1;
});
}
Map<String, int> get counters => Map.unmodifiable(_counters);
}
void main() async {
final container = Container();
container._counters['a'] = 0;
container._counters['b'] = 0;
final completer = Completer<void>();
var completedCount = 0;
void doIncrement(String name, int n) async {
for (var i = 0; i < n; i++) {
await container.inc(name);
}
completedCount++;
if (completedCount == 3) completer.complete();
}
// Run several isolates concurrently
Isolate.spawn(doIncrement, ['a', 10000]);
Isolate.spawn(doIncrement, ['a', 10000]);
Isolate.spawn(doIncrement, ['b', 10000]);
// Wait for the isolates to finish
await completer.future;
print(container.counters);
}
In this Dart version, we use a Lock
from the synchronized
package to implement mutual exclusion. The Container
class holds a map of counters, and we use the Lock
to synchronize access to this map.
The inc
method is now asynchronous, as it needs to acquire the lock before updating the counter.
Instead of goroutines, we use Dart’s Isolate
s for concurrent execution. The doIncrement
function is spawned in separate isolates to increment the counters.
We use a Completer
to wait for all isolates to finish their work before printing the final counter values.
Note that the zero value of a Lock
is usable as-is, so no initialization is required.
Running the program shows that the counters are updated as expected:
$ dart run mutexes.dart
{a: 20000, b: 10000}
Next, we’ll look at implementing this same state management task using only isolates and ports for communication.