Protecting Shared Resources with a Temporary “Lease”

When protecting shared resources across multiple threads, developers will generally reach for a “mutex.”  As a refresher; a mutex is a type of object which ‘protects’ a resource, ensuring it is only accessed by a single consumer at a time; all other consumers are forced to wait in line. They must wait for the original consumer to release the mutex, before someone else can use it.

Think of a mutex like a key; you can only access the ‘locked’ resource as long as you’re holding the key.  If someone else holds the key, you have to wait for them to put it down before you can pick it up.

With this in mind, it should go without saying that a mutex should generally be held only for the minimum amount of time absolutely necessary.  When acquiring a mutex, users are encouraged to quickly finish their work so they can release the mutex to another consumer who might be waiting in line.

To some extent, this depends greatly on the shared ‘resource’ in question.  For a shared queue, it’s easy to quickly acquire a mutex, ‘pop’ the top item off the stack, and release the mutex – all without the user’s awareness.  For other resources however, consumers may need to hold a mutex for an extended period of time, through a series of complex operations.

For example: a connected “instrument” – such as a robot arm, or stepper motor – may need to be ‘reserved’ by a consumer for an extended period of time.  It may need to perform many complex operations before the consumer is ready to release the mutex for someone else to use.

In cases like these, it is dangerous to rely on the consumer of a resource to reliably lock and unlock the mutex themselves.  It would be so much more convenient and safe, if we could somehow package the resource and the mutex together, ensuring that one is always included with the other.

Enter the “lease” object.

Requirements

The basic idea of a “lease” class is: in order to gain access to a protected resource, a consumer must acquire a “lease” from some gatekeeper or “resource provider.”  The lease automatically locks the resource via a mutex, and the consumer must access that resource through the lease itself.  Once the lease is destroyed or goes out of scope, the underlying mutex is automatically released, and the consumer can no longer access the resource.  Another consumer can request their own lease as soon as it is available.

Ideally, usage would look something like this:

We can use this block of code to stub out some requirements for our ‘lease’ class:

  • The lease must ‘lock’ a mutex, during construction
  • The lease must ‘unlock’ the mutex, during destruction
  • The lease must provide direct access to our desired resource
  • The lease object cannot be copyable or assignable, but can be moved.

Implementation

This actually isn’t too bad.  I believe we can make this into an efficient, intuitive and compact template class.  Let’s stub some code and discuss what we have:

Template Parameters

So; we have a template.  Our template takes two parameters:

  • TResource: the type of the resource which we are protecting.  We want users to be able to access it directly, without any additional casting.
  • TMutex: the type of the mutex we will lock and unlock.  This defaults to the std::mutex , but we could use any type which supports the C++ “BasicLockable” concept.

Constructors

Our lease interface is extremely limiting.  That’s intentional!  Down at the bottom in the private  access space, we can see the following operators and constructors are all deleted (disabled):

  • Copy constructor
  • Assignment operator

Great, the lease can’t be accidentally copied or re-assigned.

Additionally, our only public constructor requires its creator to provide a mutex, and a reference to the desired resource.  This ensures we’ll never have an ‘invalid’ lease.

In the constructor and destructor, we can see that the provided mutex is automatically locked and unlocked, respectively.  This satisfies our desired “RAII” behavior.

Resource Access

We provide a single getter – called get – which provides direct (by-reference) access to our resource type.

With this implementation, we’ve matched our desired usage example to a T!

Creating A Lease

The only thing left to do is look at how the lease is created.  This code represents our “gatekeeper” class – the ResourceProvider from our initial example.

Our ResourceProvider will most likely be the ‘host’ of our underlying resource.  It ‘owns’ the resource and its associated mutex, but only provides access to the resource via a lease-granting API.

That was… exceedingly short and simple.

2 Replies to “Protecting Shared Resources with a Temporary “Lease””

  1. Time for you to school me again.

    Near the start of the article you propose using a mutex to acquire a stack, to manipulate it, but I’ve always looked at popping the top (or copying the top item without popping) as an alternative to using mutexes entirely. Is it really necessary to protect the stack if all I want to do is pop the top?

    The lease wrapper is a great paradigm for easily protecting sloppy consumers (sometimes guilty 😁).

    1. “It depends.” But it probably matters; std::stack and std::queue are not thread-safe, out of the box.

      • How many consumer threads are ‘topping’ and ‘popping’ from the container at once?
      • How many producer threads are ‘pushing’ into the container at once?
      • What objects are being stored in the container? Can those objects be copied atomically?

      Since std::queue and std::stack generally store copies of an object, it can take multiple CPU instructions to call ‘push’ or ‘top’ or ‘pop’. One thread could be in the middle of copying the ‘top’ item, when another thread ‘pops’ that item out of existence. Now the first thread is copying invalid memory. Locking on a mutex helps ensure that the queue remains stable for the (hopefully short) period of time in which each thread needs stability.

      Copying the top item out of the container and immediately popping it out is a great approach, but there’s still a slim window of opportunity for a bug, if the container is not locked down for that operation.

Leave a Reply to Cheery Brian Cancel reply

Your email address will not be published. Required fields are marked *