get_mutex 1.0.0 copy "get_mutex: ^1.0.0" to clipboard
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 #

Pub Version codecov

πŸ”’ 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 #

  1. Add GetMutex to your pubspec.yaml:

    dependencies:
      get_mutex: ^1.0.0
    
  2. Run: dart pub get

  3. 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'));
});

License #

GetMutex is released under the MIT License. See the LICENSE file for details.

2
likes
140
points
36
downloads

Publisher

unverified uploader

Weekly Downloads

Robust, efficient, and deadlock-free synchronization in Dart. Concurrency made simple, without sacrificing performance.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

More

Packages that depend on get_mutex