get_mutex 1.0.0 get_mutex: ^1.0.0 copied to clipboard
Robust, efficient, and deadlock-free synchronization in Dart. Concurrency made simple, without sacrificing performance.
GetMutex: Bulletproof Concurrency for Dart #
π GetMutex is your solution for robust, efficient, and deadlock-free synchronization in Dart. Concurrency made simple, without sacrificing performance.
Why GetMutex? #
In the world of concurrent programming, race conditions can wreak havoc on your code, leading to unpredictable outcomes. GetMutex is here to eliminate those risks while offering:
- π High Performance: Outperforms naive
await
-based solutions by up to 2x in our benchmarks. - π‘οΈ Reliable Protection: Locks down your critical sections with precision, ensuring data integrity.
- π§ Simple API: User-friendly, intuitive API that makes concurrency less daunting.
- π¬ 100% Test Coverage: We take your data's safety seriously, and it shows.
Letβs dive into a real-world example to illustrate why GetMutex is indispensable.
The Bank Account Problem: A Case of Concurrent Chaos #
Imagine you're building a banking app. Without proper synchronization, concurrent transfers can lead to financial discrepancies. Hereβs what happens without GetMutex:
Without Synchronization (Prone to Race Conditions) #
Imagine you're building a banking app. Without proper synchronization, you're one concurrent transfer away from a financial disaster. Let's see what happens without GetMutex:
import 'dart:async';
class BankAccount {
final String accountNumber;
double balance;
BankAccount(this.accountNumber, this.balance);
Future<bool> transfer(BankAccount targetAccount, double amount) async {
if (amount > balance) {
return false; // Insufficient funds
}
await Future.delayed(Duration(milliseconds: 50)); // Simulating processing time
balance -= amount;
await Future.delayed(Duration(milliseconds: 50)); // Simulate some delay
targetAccount.balance += amount;
return true;
}
}
void main() async {
final accountA = BankAccount('Account A', 1000);
final accountB = BankAccount('Account B', 500);
// Simulate multiple concurrent transfers
final transfers = [
accountA.transfer(accountB, 200), // Account A: $800, Account B: $700
accountA.transfer(accountB, 300), // Account A: $500, Account B: $1000
accountB.transfer(accountA, 100), // Account A: $600, Account B: $900
accountB.transfer(accountA, 700), // Account A: $1300, Account B: $200
];
final results = await Future.wait(transfers);
print('Transfer Results:');
for (var result in results) {
print(result ? 'Transfer succeeded' : 'Transfer failed');
}
// Possible Output:
// Transfer succeeded
// Transfer succeeded
// Transfer succeeded
// Transfer failed
print('Final Balances:');
print('${accountA.accountNumber}: \$${accountA.balance}');
print('${accountB.accountNumber}: \$${accountB.balance}');
// Possible Output:
// Account A: $600.0
// Account B: $900.0
}
The Problem #
You might expect the final balances to be:
- Account A: $1300.0
- Account B: $200.0
But due to race conditions, you could end up with:
- Account A: $600.0
- Account B: $900.0
Money has been created out of thin air! πΈ This is because multiple transfers are happening concurrently without proper synchronization.
The "Await" Solution (Spoiler: It's Slow) #
You might think, "I'll just use await
before each operation!" Sure, it works, but at what cost?
void main() async {
final accountA = BankAccount('Account A', 1000);
final accountB = BankAccount('Account B', 500);
final clockWatch = Stopwatch()..start();
await accountA.transfer(accountB, 200);
await accountA.transfer(accountB, 300);
await accountB.transfer(accountA, 100);
await accountB.transfer(accountA, 700);
clockWatch.stop();
print('Elapsed time using await: ${clockWatch.elapsedMilliseconds} ms');
// Output: Elapsed time using await: 209 ms
}
Enter GetMutex: Safe and Swift #
Now, let's see how GetMutex handles this with grace and speed:
import 'package:get_mutex/get_mutex.dart';
class BankAccount {
final String accountNumber;
double balance;
final mutex = Mutex();
BankAccount(this.accountNumber, this.balance);
Future<bool> transfer(BankAccount targetAccount, double amount) async {
return await mutex.protectWrite(() async {
if (amount > balance) return false;
balance -= amount;
await targetAccount.deposit(amount);
return true;
});
}
Future<void> deposit(double amount) async => balance += amount;
}
void main() async {
final accountA = BankAccount('Account A', 1000);
final accountB = BankAccount('Account B', 500);
final transfers = [
accountA.transfer(accountB, 200),
accountA.transfer(accountB, 300),
accountB.transfer(accountA, 100),
accountB.transfer(accountA, 700),
];
final clockWatch = Stopwatch()..start();
await Future.wait(transfers);
clockWatch.stop();
print('Elapsed time using mutex: ${clockWatch.elapsedMilliseconds} ms');
// Output: Elapsed time using mutex: 107 ms
print('Final Balances:');
print('${accountA.accountNumber}: \$${accountA.balance}'); // Account A: $1300.0
print('${accountB.accountNumber}: \$${accountB.balance}'); // Account B: $200.0
}
With GetMutex, not only are your accounts safe and your transfers atomic, but you're also doing it in half the time! π
Features That'll Make You Smile #
- π Simple and Reentrant Mutexes: Lock it once, lock it twice, we've got you covered.
- π Read-Write Locks: Because sometimes, sharing is caring (and performant).
- β±οΈ Timeouts and Cancellation: Because even mutexes shouldn't wait forever.
- ποΈ Flexible Policies: Fair, readers-first, or writers-first β you're in control.
Getting Started #
-
Add GetMutex to your
pubspec.yaml
:dependencies: get_mutex: ^1.0.0
-
Run:
dart pub get
-
Import and enjoy:
import 'package:get_mutex/get_mutex.dart'; final mutex = Mutex(); await mutex.protectWrite(() { // Your critical section here print('Writing safely!'); });
Advanced Usage #
GetMutex grows with your needs. Need more control? We've got you covered:
final mutex = Mutex();
// Optimize for multiple readers
await mutex.protectRead(() => print('Reading in parallel'));
// Ensure exclusive access for writers
await mutex.protectWrite(() => print('Writing exclusively'));
// Set a timeout to avoid deadlocks
await mutex.protectWrite(() => longOperation(), timeout: Duration(seconds: 5));
// Use cancellation for more control
final token = CancellationToken();
await mutex.protectWrite(() => cancelableOperation(), cancellationToken: token);
We also support raw read-write locks for fine-grained control over your shared resources, but with the same ease of use (however, we recommend using Mutex
for most cases, RawReadWriteMutex is more low-level, and HAVE NO SUPPORT for reentrant locks):
final rwMutex = RawReadWriteMutex();
await rwMutex.protectRead(() => print('Reading in parallel'));
await rwMutex.protectWrite(() => print('Writing exclusively'));
Future<T> myCustomProtectWrite<T>(
Future<T> Function() action, {
CancellationToken? cancellationToken,
Duration? timeout,
}) async {
await rwMutex.acquireWrite(cancellationToken: cancellationToken, timeout: timeout);
try {
return await action();
} finally {
rwMutex.releaseWrite();
}
}
Do you want keep it simple? We have a SimpleMutex/SimpleReentrantMutex for you:
final simpleMutex = SimpleMutex();
await simpleMutex.protect(() => print('Simple Mutex'));
final simpleReentrantMutex = SimpleReentrantMutex();
await simpleReentrantMutex.protect(() {
print('Simple Reentrant Mutex');
simpleReentrantMutex.protect(() => print('Reentrant Mutex inside'));
});
For sake of simplicity, we have an alias
GetMutex class that has all available mutexes:
final mutex = GetMutex.mutex();
await mutex.protectWrite(() => print('Writing safely!'));
final rwMutex = GetMutex.rawReadWriteMutex();
await rwMutex.protectRead(() => print('Reading in parallel'));
final simpleMutex = GetMutex.simpleMutex();
await simpleMutex.protect(() => print('Simple Mutex'));
final simpleReentrantMutex = GetMutex.simpleReentrantMutex();
await simpleReentrantMutex.protect(() {
print('Simple Reentrant Mutex');
simpleReentrantMutex.protect(() => print('Reentrant Mutex inside'));
});
- π Found a bug? Open an issue.
- π‘ Have an idea? Submit a pull request.
- π€ Questions? Start a discussion.
License #
GetMutex is released under the MIT License. See the LICENSE file for details.