Skip to content

Repository files navigation

This repository for examples of articles

How to make any object thread-safe with the speed of lock-free algorithms

safe_ptr.h contains C++ code of:

  • safe_ptr<> - make any your custom object thread-safe, even when passed to a function

  • contention_free_shared_mutex<> - 10 X times faster than std::shared_mutex

  • contfree_safe_ptr<> - make any your custom object thread-safe with the speed of lock-free algorithms


Performance, MOpsMedian Latency, usec

Additional contents for articles

There are examples:

There are benchmarks:


For your Pull Requests use a separate branch: https://github.com/AlexeyAB/object_threadsafe/tree/for_pull_requests

Before Pull Requests - please run all necessary benchmarks, preferably on a server CPU, such as in folders: CDS_test, Real_app_bench or benchmark - and insert images of the results charts.


  1. How to use safe_ptr<> - in the same way as std::shared_ptr<>: http://coliru.stacked-crooked.com/a/ccf634f1a5e7f991
#include<iostream>
#include<thread>
#include<vector>
#include<string>
#include<map>
#include"safe_ptr.h"intmain() { sf::safe_ptr< std::map< std::string, int > > safe_map_string_int;
std::thread t1([&]() { safe_map_string_int->emplace("apple", 1); }); std::thread t2([&]() { safe_map_string_int->emplace("potato", 2); }); t1.join(); t2.join();
std::cout << "apple = " << (*safe_map_string_int)["apple"] << ", potato = " << (*safe_map_string_int)["potato"] << std::endl; return0;
}

  1. How to use sf::contention_free_shared_mutex - in the same way as std::shared_mutex: http://coliru.stacked-crooked.com/a/11c191b06aeb5fb6
#include<iostream>
#include<thread>
#include<vector>
#include"safe_ptr.h"template <typename T>
voidfunc(T &s_m, int &a, int &b)
{
for (size_t i = 0; i < 100000; ++i)
{
// x-lock for modification
{
s_m.lock();
a++; b++;
s_m.unlock();
}
// s-lock for reading
{
s_m.lock_shared();
assert(a == b); // will never happen
s_m.unlock_shared();
}
}
}
intmain() {
int a = 0;
int b = 0;
sf::contention_free_shared_mutex< > s_m;
// 20 threads
std::vector< std::thread > vec_thread(20);
for (auto &i : vec_thread) i = std::move(std::thread([&]() { func(s_m, a, b); }));
for (auto &i : vec_thread) i.join();
std::cout << "a = " << a << ", b = " << b << std::endl;
getchar();
return0;
}

2.1. How to use contfree_safe_ptr< std::map >: http://coliru.stacked-crooked.com/a/b78467b7a3885e5b

#include"safe_ptr.h"
sf::contfree_safe_ptr< std::map<std::string, int> > safe_map_strings_global; // cont-free shared-mutextemplate<typename T>
voidfunc(sf::contfree_safe_ptr<T> safe_map_strings)
{
// read-only (shared lock during access)
sf::contfree_safe_ptr<T> const &readonly_safe_map_string = safe_map_strings;
for (size_t i = 0; i < 100000; ++i)
{
// two Shared locks (recursive)assert(readonly_safe_map_string->at("apple") == readonly_safe_map_string->at("potato")); std::lock_guard<decltype(safe_map_strings)> lock(safe_map_strings); // 1-st eXclusive lock
safe_map_strings->at("apple") += 1; // 2-nd recursive eXclusive lock
safe_map_strings->find("potato")->second += 1; // 3-rd recursive eXclusive lock
}
}
intmain() {
(*safe_map_strings_global)["apple"] = 0;
(*safe_map_strings_global)["potato"] = 0;
std::vector<std::thread> vec_thread(10);
for (auto &i : vec_thread) i = std::move(std::thread([&]() { func(safe_map_strings_global); }));
for (auto &i : vec_thread) i.join();
std::cout << "END: potato is " << safe_map_strings_global->at("potato") <<
", apple is " << safe_map_strings_global->at("apple") << std::endl;
return0;
}

  1. High-performance usage of contfree_safe_ptr< std::map > and safe_obj<>: http://coliru.stacked-crooked.com/a/f7079aa1acc00a78
#include"safe_ptr.h"usingnamespacesf;structfield_t { int money, time; field_t(int m, int t) : money(m), time(t) {} field_t() : money(0), time(0) {} };
typedef safe_obj<field_t, spinlock_t> safe_obj_field_t;
contfree_safe_ptr< std::map<int, safe_obj_field_t> > safe_map_contfree_rowlock_global;
template<typename T>
voidbenchmark_safe_ptr_rowlock(T safe_map, size_tconst iterations_count, size_tconst container_size)
{
constunsignedint seed = (unsigned)std::chrono::system_clock::now().time_since_epoch().count();
std::default_random_engine generator(seed);
std::uniform_int_distribution<size_t> index_distribution(0, container_size);
enum { insert_op, delete_op, update_op, read_op };
std::uniform_int_distribution<size_t> operation_distribution(insert_op, read_op); // 0 - 4for (size_t i = 0; i < iterations_count; ++i) {
intconst rnd_index = index_distribution(generator); // 0 - container_sizeintconst num_op = operation_distribution(generator); // insert_op, update_op, delete_op, read_opswitch (num_op) {
case insert_op: {
slock_safe_ptr(safe_map)->find(rnd_index); // find for pre-cache to L1 with temprorary S-lock
safe_map->emplace(rnd_index, (field_t(rnd_index, rnd_index))); // insert with X-lock on Tablebreak;
}
case delete_op: {
slock_safe_ptr(safe_map)->find(rnd_index); // find for pre-cache to L1 with temprorary S-locksize_t erased_elements = safe_map->erase(rnd_index); // erase with X-lock on Table
}
break;
case update_op: {
auto s_safe_map = slock_safe_ptr(safe_map); // S-lock on Tableauto it = s_safe_map->find(rnd_index);
if (it != s_safe_map->cend()) {
auto x_field = xlock_safe_ptr(it->second);
x_field->money += rnd_index; // X-lock on field, still S-lock on Table (must necessarily be)
}
}
break;
case read_op: {
auto s_safe_map = slock_safe_ptr(safe_map); // S-lock on Tableauto it = s_safe_map->find(rnd_index);
if (it != s_safe_map->cend()) {
auto s_field = slock_safe_ptr(it->second);
volatileint money = s_field->money; // S-lock on field, still S-lock on Table (must be)// volatile here only to avoid optimization for unused money-variable
}
}
break;
default: std::cout << "\n wrong way! \n"; break;
}
}
}
intmain() {
constsize_t iterations_count = 100000; // operations of data exchange between threadsconstsize_t container_size = 100000; // elements in container
std::vector<std::thread> vec_thread(8); // threads number
std::cout << "Threads: " << vec_thread.size() << std::endl;
std::cout << "Testing safe<map,contf>rowlock... \n"; for (auto &i : vec_thread) i = std::move(std::thread([&]() {
benchmark_safe_ptr_rowlock(safe_map_contfree_rowlock_global, iterations_count, container_size);
}));
for (auto &i : vec_thread) i.join();
std::cout << "Successfully completed." << std::endl;
return0;
}

About

We make any object thread-safe and std::shared_mutex 10 times faster to achieve the speed of lock-free algorithms on >85% reads

Topics

Resources

Stars

522 stars

Watchers

28 watching

Forks

Releases

Packages

Used by

Contributors

Languages