Mutual Exclusion with Implementation Of Normal and Read-write Mutex

mutex 

A library for creating locks to ensure mutual exclusion when running critical sections of code.

Purpose

Mutexes can be used to protect critical sections of code to prevent race conditions.

Although Dart uses a single thread of execution, race conditions can still occur when asynchronous operations are used inside critical sections. For example,

x = 42;
synchronousOperations(); // this does not modify x
assert(x == 42); // x will NOT have changed

y = 42; // a variable that other asynchronous code can modify
await asynchronousOperations(); // this does NOT modify y, but...
// There is NO GUARANTEE other async code didn't run and change it!
assert(y == 42 || y != 42); // WARNING: y might have changed

An example is when Dart is used to implement a server-side Web server that updates a database (assuming database transactions are not being used). The update involves querying the database, performing calculations on those retrieved values, and then updating the database with the result. You don't want the database to be changed by "something else" while performing the calculations, since the results you would write will not incorporate those other changes. That "something else" could be the same Web server handling another request in parallel.

This package provides a normal mutex and a read-write mutex.

Mutex

A mutex guarantees at most only one lock can exist at any one time.

If the lock has already been acquired, attempts to acquire another lock will be blocked until the lock has been released.

import 'package:mutex/mutex.dart';

...

final m = Mutex();

Acquiring the lock before running the critical section of code, and then releasing the lock.

await m.acquire();
// No other lock can be acquired until the lock is released

try {
  // critical section with asynchronous code
  await ...
} finally {
  m.release();
}

protect

The following code uses the protect convenience method to do the same thing as the above code. Use the convenence method whenever possible, since it ensures the lock will always be released.

await m.protect(() async {
  // critical section
});

If the critial section returns a Future to a value, the protect convenience method will return a Future to that value.

final result = await m.protect<int>(() async {
  // critical section
  return valueFromCriticalSection;
});
// result contains the valueFromCriticalSection

Read-write mutex

A read-write mutex allows multiple reads locks to be exist simultaneously, but at most only one write lock can exist at any one time. A write lock and any read locks cannot both exist together at the same time.

If there is one or more read locks, attempts to acquire a write lock will be blocked until all the read locks have been released. But attempts to acquire more read locks will not be blocked. If there is a write lock, attempts to acquire any lock (read or write) will be blocked until that write lock is released.

A read-write mutex can also be described as a single-writer mutex, multiple-reader mutex, or a reentrant lock.

import 'package:mutex/mutex.dart';

...

final m = ReadWriteMutex();

Acquiring a write lock:

await m.acquireWrite();
// No other locks (read or write) can be acquired until released

try {
  // critical write section with asynchronous code
  await ...
} finally {
  m.release();
}

Acquiring a read lock:

await m.acquireRead();
// No write lock can be acquired until all read locks are released,
// but additional read locks can be acquired.

try {
  // critical read section with asynchronous code
  await ...
} finally {
  m.release();
}

protectWrite and protectRead

The following code uses the protectWrite and protectRead convenience methods to do the same thing as the above code. Use the convenence method whenever possible, since it ensures the lock will always be released.

await m.protectWrite(() async {
  // critical write section
});

await m.protectRead(() async {
  // critical read section
});

If the critial section returns a Future to a value, these convenience methods will return a Future to that value.

final result1 await m.protectWrite<String>(() async {
  // critical write section
  return valueFromCritialSection1;
});
// result1 contains the valueFromCriticalSection1

final result2 = await m.protectRead(() async {
  // critical read section
  return valueFromCritialSection2;
});
// result2 contains the valueFromCriticalSection2

When mutual exclusion is not needed

The critical section should always contain some asynchronous code. If the critical section only contains synchronous code, there is no need to put it in a critical section. In Dart, synchronous code cannot be interrupted, so there is no need to protect it using mutual exclusion.

Also, if the critical section does not involve data or shared resources that can be accessed by other asynchronous code, it also does not need to be protected. For example, if it only uses local variables that other asynchronous code won't have access to: while the other asynchronous code could run, it won't be able to make unexpected changes to the local variables it can't access.

Features and bugs

Please file feature requests and bugs at the issue tracker.

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add mutex

With Flutter:

 $ flutter pub add mutex

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):

dependencies:
  mutex: ^3.0.1

Alternatively, your editor might support dart pub get or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:mutex/mutex.dart';

example/example.dart

// Mutex example.
//
// This example demonstrates why a mutex is needed.

import 'dart:async';
import 'dart:math';
import 'package:mutex/mutex.dart';

//----------------------------------------------------------------
// Random asynchronous delays to try and simulate race conditions.

const _maxDelay = 500; // milliseconds

final _random = Random();

Future<void> randomDelay() async {
  await Future<void>.delayed(
      Duration(milliseconds: _random.nextInt(_maxDelay)));
}

//----------------------------------------------------------------
/// Account balance.
///
/// The classical example of a race condition is when a bank account is updated
/// by different simultaneous operations.

int balance = 0;

//----------------------------------------------------------------
/// Deposit without using mutex.

Future<void> unsafeUpdate(int id, int depositAmount) async {
  // Random delay before updating starts
  await randomDelay();

  // Add the deposit to the balance. But this operation is not atomic if
  // there are asynchronous operations in it (as simulated by the randomDelay).

  final oldBalance = balance;
  await randomDelay();
  balance = oldBalance + depositAmount;

  print('  [$id] added $depositAmount to $oldBalance -> $balance');
}

//----------------------------------------------------------------
/// Deposit using mutex.

Mutex m = Mutex();

Future<void> safeUpdate(int id, int depositAmount) async {
  // Random delay before updating starts
  await randomDelay();

  // Acquire the mutex before running the critical section of code

  await m.protect(() async {
    // critical section

    // This is the same as the unsafe update. But since it is performed only
    // when the mutex is acquired, it is safe: no other safe update can happen
    // until this mutex is released.

    final oldBalance = balance;
    await randomDelay();
    balance = oldBalance + depositAmount;

    // end of critical section

    print('  [$id] added $depositAmount to $oldBalance -> $balance');
  });
}

//----------------------------------------------------------------
/// Make a series of deposits and see if the final balance is correct.

Future<void> makeDeposits({bool safe = true}) async {
  print(safe ? 'Using mutex:' : 'Not using mutex:');

  const numberDeposits = 10;
  const amount = 10;

  balance = 0;

  // Create a set of operations, each attempting to deposit the same amount
  // into the account.

  final operations = <Future>[];
  for (var x = 0; x < numberDeposits; x++) {
    final f = (safe) ? safeUpdate(x, amount) : unsafeUpdate(x, amount);
    operations.add(f);
  }

  // Wait for all the deposit operations to finish

  await Future.wait<void>(operations);

  // Check if all of the operations succeeded

  final expected = numberDeposits * amount;
  if (balance != expected) {
    print('Error: deposits were lost (final balance $balance != $expected)');
  } else {
    print('Success: no deposits were lost');
  }
}

//----------------------------------------------------------------

void main() async {
  await makeDeposits(safe: false);
  print('');
  await makeDeposits(safe: true);
}

Download details:

Author: hoylen.com 

Source: https://github.com/hoylen/dart-mutex

#flutter #android #web-development #web #dart #coding 

Mutual Exclusion with Implementation Of Normal and Read-write Mutex
1.50 GEEK