All Projects → LouisCharlesC → safe

LouisCharlesC / safe

Licence: MIT License
C++11 header only RAII guards for mutexes and locks.

Programming Languages

C++
36643 projects - #6 most used programming language
CMake
9771 projects

Projects that are alternatives of or similar to safe

TAOMP
《多处理器编程的艺术》一书中的示例代码实现,带有注释与单元测试
Stars: ✭ 39 (-67.23%)
Mutual labels:  multi-threading, lock
AtomicKit
Concurrency made simple in Swift.
Stars: ✭ 88 (-26.05%)
Mutual labels:  lock, thread-safety
basicLibPP
A powerful library for inline-hook,lock,compress etc,and it is useful for anti-virus software.
Stars: ✭ 15 (-87.39%)
Mutual labels:  lock
fork-helper
A PHP helper to fork processes and allow multi-threading
Stars: ✭ 69 (-42.02%)
Mutual labels:  multi-threading
jsonpyes
The tool which imports raw JSON to ElasticSearch in one line of commands
Stars: ✭ 67 (-43.7%)
Mutual labels:  multi-threading
angular-lock
No description or website provided.
Stars: ✭ 16 (-86.55%)
Mutual labels:  lock
glfwm
GLFW Manager - C++ wrapper with multi-threading
Stars: ✭ 60 (-49.58%)
Mutual labels:  multi-threading
mbapipy
MercedesME platform as a Custom Component for Home Assistant.
Stars: ✭ 25 (-78.99%)
Mutual labels:  lock
toxic-decorators
Library of Javascript decorators
Stars: ✭ 26 (-78.15%)
Mutual labels:  lock
discord-chat-guard-bot
A simple Discord chat guard bot.
Stars: ✭ 37 (-68.91%)
Mutual labels:  guard
slow-down
A centralized Redis-based lock to help you wait on throttled resources
Stars: ✭ 21 (-82.35%)
Mutual labels:  lock
aiorwlock
Read/Write Lock - synchronization primitive for asyncio
Stars: ✭ 90 (-24.37%)
Mutual labels:  lock
futex
File-based Ruby Mutex
Stars: ✭ 14 (-88.24%)
Mutual labels:  lock
Bluetooth-Unlock
Simple script to unlock your Linux based Computer using a Bluetooth device when nearby
Stars: ✭ 37 (-68.91%)
Mutual labels:  lock
label-actions
🤖 GitHub Action that performs certain tasks when issues, pull requests or discussions are labeled or unlabeled
Stars: ✭ 60 (-49.58%)
Mutual labels:  lock
scope-guard
Implementation of Scoped Guards and Unique Resource as proposed in P0052.
Stars: ✭ 22 (-81.51%)
Mutual labels:  raii
request store rails
📦 Per-request global storage for Rails prepared for multi-threaded apps
Stars: ✭ 78 (-34.45%)
Mutual labels:  multi-threading
lfqueue
Minimize lock-free queue ever!
Stars: ✭ 107 (-10.08%)
Mutual labels:  thread-safety
scroll-padlock
🔒 CSS variables-based scrollbars locker, compatible with all reactive frameworks
Stars: ✭ 12 (-89.92%)
Mutual labels:  lock
django-admin-page-lock
Page Lock for Django Admin allows developers to implement customizable locking pages.
Stars: ✭ 13 (-89.08%)
Mutual labels:  lock

Every variable protected by a mutex should be wrapped with safe.

Build Status Build status

Contents

safe is a header-only library that makes code with mutexes safer and easier to understand.
This readme will walk you through the important features of the library using several code examples. Read on, and enjoy safe mutexes!

Overview

Two class templates are at the core of safe: Safe and Access. Safe objects pack a mutex and a value object together. Access objects act as a lock (e.g. std::lock_guard) for the mutex and provide pointer-like access to the value object. You will learn more about Safe and Access here.

Here is why you want to use safe:

Mutex code without safe

std::string foo; // do I need to lock a mutex to safely access this variable ?
std::string bar;
std::string baz; // what about this one ?
std::mutex fooMutex; // don't forget to change the name of this variable if foo's name changes!
std::mutex barMutex;

{
	std::lock_guard<std::mutex> lock(fooMutex); // is this the right mutex for what I am about to do ?
	foo = "Hello, World!"; // I access foo here, but I could very well access bar, yet barMutex is not locked!
}

std::cout << bar << std::endl; // unprotected access, is this intended ?
std::cout << baz << std::endl; // what about this access ?

Mutex code with safe

using SafeString = safe::Safe<std::string>; // type aliases will save you a lot of typing
SafeString safeFoo; // std::string and mutex packaged together!
SafeString safeBar;
std::string baz; // now you can see that this variable has no mutex

{
	safe::WriteAccess<SafeString> foo(safeFoo); // this locks the mutex and gives you access to foo, nothing more
	*foo = "Hello, World!"; // access the value using pointer semantics: * and ->
}

//std::cout << safeBar << std::endl; // does not compile!
std::cout << safeBar.unsafe() << std::endl; // unprotected access: clearly expressed!
std::cout << baz << std::endl; // all good (remember, baz has no mutex!)

Vocabulary

  • safe: the library.
  • mutex: a mutex like std::mutex.
  • value object: whatever needs to be protected by the mutex.
  • Safe object: combines a value object and a mutex.
  • lock: an object that manages a mutex using RAII like std::lock_guard and std::unique_lock.
  • Access object: a lock object that also gives pointer-like access to a value object.
  • access mode: Access objects can be created with read-write or read-only behavior. Read-only Access objects are especially useful to enforce the read-only nature of C++14's std::shared_lock and boost::shared_lock_guard.

Motivation

Since C++11, the standard library provides mutexes, like std::mutex, along with tools to facilitate their usage, like std::lock_guard and std::unique_lock. These are sufficient to write safe multithreaded code, but it is all too easy to write code you think is safe but actually is not. Typical mistakes are: locking the wrong mutex and accessing the value object before locking (or after unlocking) the mutex. Other minor mistakes like unnecessarily locking or keeping a mutex locked for too long can also be avoided.

safe aims at preventing common mutex usage mistakes by providing tools that complement the C++ standard library. Using safe, you will find it much easier to protect a variable using a mutex, and your code will be easier to understand.

Basic usage

The safe library defines the Safe and Access class templates. They are meant to replace the mutexes and locks in your code. safe does not offer much more functionality than mutexes and locks do, they simply make their usage safer.
Here is the simplest way to replace mutexes and locks by safe objects:

Make the library available in your build system

safe is a header-only library. Using the library can simply mean copy the contents of the include/ folder to some place of your convenience. Alternately, you could install the library using cmake. A typical install procedure is:

mkdir build
cd build
cmake ..
sudo cmake --build . --target install

Hopefully this works on any OS. It should copy the library to the right os-specific location so you can succesfully #include <safe/safe.h>. It will also create the cmake specific files that will allow you to use find_package(safe) in your cmake scripts.

Include the library's single header

#include <safe/safe.h>

Replace your values and mutexes by Safe objects

// std::mutex mutex;
// int value;
safe::Safe<int> safeValue;

Replace your lock objects by Access objects

Access objects can either be read-write or read-only. The examples below show different way to create a WriteAccess object, Replace Write/write by Read/read to get ReadAccess objects.

// std::lock_guard<std::mutex> lock(mutex);
safe::WriteAccess<safe::Safe<int>> value(safeValue);
safe::Safe<int>::WriteAccess<> value(safeValue); // equivalent to the above
auto value = safeValue.writeAccess(); // only with C++17 and later

The problem with std::lock_guard

The last line of the above example only compiles with C++17 and later. This is because of the new rules on temporaries introduced in C++17, and because safe uses std::lock_guard by default. std::lock_guard is non-copiable, non-moveable so it cannot be initialized as above prior to C++17. As shown below, using std::unique_lock (which is moveable) is fine:

auto value = safeValue.writeAccess<std::unique_lock>(); // ok even pre-C++17

Access your value object through the Access objects using pointer semantics

You can now safely access the value object through the Access object without worrying about the mutex.

// value = 42;
*value = 42;

Use Safe member functions as one-liners, if suitable

If you need to peform a single access to your value, you can do this using Safe's member functions: readAccess(), writeAccess(), copy() and assign(). readAccess() and writeAccess() will return an Access object, but will let you operate on it in an expressive way. Example:

*safeValue.writeAccess() = 42;
int value = *safeValue.readAccess();
int value = *safeValue.writeAccess(); // this also works...
// *safeValue.readAccess() = 42; // but this obviously doesn't!

However, if all you need to do is assign a new value, then you might as well use the assign function:

safeValue.assign(42);

Finally, if a copy of the value object is all you need, call the copy function:

int value = safeValue.copy();

Warning: avoid multiple calls to these functions, as each will lock and unlock the mutex. Be aware that copy/move construction/assignment operators are deleted for Safe objects. That is because copying and moving requires the mutex to be locked, and the safe library aims at making every locking explicit. Use the copy() and assign() functions instead.

Main features

1. Safety and clarity

No more locking the wrong mutex, no more mistaken access outside the safety of a locked mutex. No more naked shared variables, no more plain mutexes lying around and no more mutable keyword (ever used a member mutex variable within a const-qualified member function ?).

2. Flexibility

Choose the mutex and lock that fit your need

The Safe class template has a template parameter for the mutex:

  • use std::mutex, std::shared_mutex (C++17), name it!

The Access class template has a template parameter for the lock object:

  • use std::lock_guard, boost::shared_lock_guard, anything you want!
  • you can use the lock you need for every Access object you construct.

Store the value object and mutex inside the Safe object, or refer to existing objects

You can use any combination of reference and non-reference types for your Safe objects:

safe::Safe<int, std::mutex>;
safe::Safe<int>; // equivalent to the above, as the second template parameter defaults to std::mutex
safe::Safe<int&, std::mutex>;
safe::Safe<int, std::mutex&>;
safe::Safe<int&, std::mutex&>;

See this section for an example of using reference types to deal with legacy code.

Flexibly construct the value object and mutex

Just remember: the first argument to a Safe constructor is used to construct the mutex, the other arguments are used for the value object.
Note: when constructing a Safe object and the mutex is default constructed but the value object is not, you must pass the safe::default_construct_mutex tag or a set of curly brackets {} as the first constructor argument.
Examples:

std::mutex aMutex;

safe::Safe<int, std::mutex> bothDefault; // mutex and value are default constructed
safe::Safe<int, std::mutex&> noDefault(aMutex, 42); // mutex and value are initialized
safe::Safe<int, std::mutex&> valueDefault(aMutex); // mutex is initialized, and value is default constructed
safe::Safe<int, std::mutex> mutexDefaultTag(safe::default_construct_mutex, 42); // mutex is default constructed, and value is initialized
safe::Safe<int, std::mutex> mutexDefaultBraces({}, 42);

Flexibly construct the Lock objects

The Access constructors have a variadic parameter pack that is forwarded to the Lock object's constructor. This can be used to pass in standard lock tags such as std::adopt_lock, but also to construct your custom locks that may require additionnal arguments than just the mutex.

safe::Safe<int> safeValue; // given a Safe object
safeValue.mutex().lock(); // with the mutex already locked...
// Because the mutex is already locked, you need to pass the std::adopt_lock tag to std::lock_guard when you construct your Access object.

// No matter how you get your Access objects, you can pass arguments to the lock's constructor.
safe::WriteAccess<safe::Safe<int>> value(safeValue, std::adopt_lock);
safe::Safe<int>::WriteAccess<> value(safeValue, std::adopt_lock);
auto value = safeValue.writeAccess(std::adopt_lock); // again, only in C++17
auto value = safeValue.writeAccess<std::unique_lock>(std::adopt_lock);

3. Even more safety!

Choose the access mode that suits each access

You will instatiate one Safe object for every value object you want to protect. But, you will create an Access object every time you want to operate on the value object. For each of these accesses, you can choose whether the access is read-write or read-only.

Force read-only access with shared mutexes and shared_locks

Shared mutexes and shared locks allow multiple reading threads to access the value object simultaneously. Unfortunately, using only mutexes and locks, the read-only restriction is not guaranteed to be applied. That is, it is possible to lock a mutex in shared mode and write to the shared value. With safe, you can enforce read-only access when using shared locking by using ReadAccess objects. See this section for details.

4. Compatibility

With legacy code

You can use safe with old-style unsafe code that uses the soon to be out of fashion separate mutex and value idiom. Imagine you are provided with the typical mutex and int. safe allows you to wrap these variables, without having to modify the existing code. Enjoy the safety and avoid the headaches:

std::mutex lousyMutex;
int unsafeValue;

// Wrap the existing variables
safe::Safe<int&, std::mutex&> safeValue(lousyMutex, unsafeValue);
// do not use lousyMutex and unsafeValue directly from here on!

With code from the future

safe is written in C++11, but it is fully compatible with mutexes and locks from different sources like C++14's std::shared_lock and C++17's std::shared_mutex, thanks to template parameters. Of course, you can also use boost::shared_lock_guard and your own custom mutexes and locks.

With standard uses of mutexes and locks

The mutex is accessible from the Safe object through an accessor functions, and the lock object is a public member of the Access class. Anything you can do with your typical mutexes and locks you can do with safe.

For example, safe can seamlessly be used with std::condition_variable:

std::condition_variable cv;
safe::Safe<int> safeValue;
safe::Safe<int>::WriteAccess<std::unique_lock> value(safeValue);
cv.wait(value.lock, [](){return true;});

Advanced usage

Enforcing read-only access

You can inform the safe library that some locks that you use are read-only (e.g. std::shared_lock, boost::shared_lock_guard). If you do so, trying to instantiate a WriteAccess object with these locks will trigger a compilation error. Use the trait class safe::LockTraits to customize this behavior.

Here is how the trait works:

  • If no specialization of the type trait exists for a lock type, the lock can be used with read-write and read-only Access objects.
  • If a specialization exists, it must declare the IsReadOnly boolean variable.
    • If IsReadOnly is true, the lock is read-only: constructinfg an Access object with this lock using Mode = AccessMode::ReadWrite will fail to compile.
    • If IsReadOnly is false, the result is the same as if no specialization exists.

As an example, here is how to specialize the trait for std::shared_lock (you will find this exact code snippet in safe/traits.h):

template<typename MutexType>
struct AccessTraits<std::shared_lock<MutexType>>
{
	static constexpr bool IsReadOnly = true;
};

Acknowledgment

Thanks to all contributors, issue raisers and stargazers! Most cmake code comes from this repo: https://github.com/bsamseth/cpp-project and Craig Scott's CppCon 2019 talk: Deep CMake for Library Authors. Many thanks to the authors!

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].