Mutexed 0.0.1
A header-only mutex-protected value wrapper for C++20.
Loading...
Searching...
No Matches
Mutexed

A header-only mutex-protected value wrapper for C++20.

Rationale

Using mutexes on their own to protect data from concurrent access impose doing specific actions that may be forgotten by the user, making it error-prone :

std::vector<std::string> users;
std::mutex users_mutex;
void handle_socket(/* ... */)
{
/*
very complicated things
that may make you forget
your duties
*/
// OOPS ! forgot to lock
users.push(user);
// what should have happen
{
std::lock_guard<std::mutex> guard(users_mutex);
users.push(user);
}
}

The Mutexed class prevents the user from accessing the data without its dedicated mutex being locked.

API Overview

Two ways of accessing the protected data are provided :

With structured bindings

using locked() :

void add_word(std::string_view word) {
auto [lock, words] = protected_words.locked();
words += ", ";
words += word;
}
The Mutexed class is a value-wrapper that protects its value with a mutex that will be referred to in...
Definition mutexed.hpp:162
decltype(auto) locked()
Provides access to the wrapped value through a tuple of an unspecified lock guard and a reference to ...
Definition mutexed.hpp:415

Note that the type of words is std::string&.

Passing a lambda

using with_locked() :

void add_word(std::string_view word) {
protected_words.with_locked([](std::string& words) {
words += ", ";
words += word;
});
}
decltype(auto) with_locked(F &&f) const
Calls f with a const& or a copy of the wrapped value while locking the inner mutex.
Definition mutexed.hpp:270

Shared-lockability

The Mutexed class detects if the mutex is shared_lockable (a concept that checks if it has the lock_shared()-associated functions) and uses lock_shared() on each of the following circumstances :

  • lock_const() is called
  • with_locked() is called with a functor that accepts a const&
  • the Mutexed is const when any of with_locked(), locked() or when_all_locked() is called

Acquiring more than one Mutexed

Warning
  • Subject to breaking changes because I think that having the function as last argument would be best but did not allocate enough time to find out how to do it
  • It does not notify the condition-variable yet

Acquiring more that one mutex is error-prone, that is why the standard library provides the free function std::lock().

The llh::mutexed::with_all_locked pseudo-free-function calls std::lock() under the hood and is used like this :

llh::mutexed::with_all_locked([](auto& data_from_a, auto const& data_from_b) {
/* use data_from_b to modify data_from_a */
},
mutexed_a,
std::cref(mutexed_b) // passing a const& will make it use lock_shared() when it exists
);

The Waiting API

You may optionally have your Mutexed object hold a condition-variable by providing has_cv as its last template argument.

See also
Detailed description in The waiting feature module.

Notifications

The non-const versions of with_locked() and locked() will call notify_all() on the condition-variable after the mutex have been unlocked.

Waiting

The Mutexed class has the three member-functions

  • wait(Predicate&&)
  • wait_for(std::chrono::duration const&, Predicate&&)
  • wait_until(std::chrono::time_point const&, Predicate&&)

that mirror the standard library's member functions of std::condition_variable_any called with a lock that is shared if the mutex is shared_lockable .

Performance

The tests confirm that the number of times the inner mutex is acquired is exactly once for both of the ways to access the protected data.

Compatibility

This library currently requires C++20.

It has been tested in the following environments :

  • Debian-based distribution with Clang-15 and libc++

TODOs

  • test on more environments
  • copy and move constructors
  • test auto const for the structured bindings
  • documentation as Github pages
  • installation guide
  • make with_all_locked take the function as its last argument instead of first
  • provide a structure bindings API for acquiring several Mutexeds
  • constructor with 2 parameter packs to construct in place both the value and the mutex
  • specialize for libcoro's coro::mutex
  • make the code compatible with C++17 using #ifdefs