Skip to content

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

License

Notifications You must be signed in to change notification settings

sv-91/object_threadsafe

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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


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"


int main() { 
    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;    
    return 0;
}

  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>
void func(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();
		}
	}
}

int main() {

	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();

	return 0;
}

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-mutex


template<typename T>
void func(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
    }
}

int main() {

    (*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;


    return 0;
}

  1. High-performance usage of contfree_safe_ptr< std::map > and safe_obj<>: http://coliru.stacked-crooked.com/a/f7079aa1acc00a78
#include "safe_ptr.h"

using namespace sf;

struct field_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>
void benchmark_safe_ptr_rowlock(T safe_map, size_t const iterations_count, size_t const container_size)
{
    const unsigned int 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 - 4

    for (size_t i = 0; i < iterations_count; ++i) 
    {
        int const rnd_index = index_distribution(generator);    // 0 - container_size
        int const num_op = operation_distribution(generator);   // insert_op, update_op, delete_op, read_op

        switch (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 Table
            break;
        }
        case delete_op: {
            slock_safe_ptr(safe_map)->find(rnd_index);  // find for pre-cache to L1 with temprorary S-lock
            
            size_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 Table
            auto 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 Table
            auto it = s_safe_map->find(rnd_index);
            if (it != s_safe_map->cend()) {
                auto s_field = slock_safe_ptr(it->second);
                volatile int 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;
        }
    }
}


int main() {

    const size_t iterations_count = 100000;     // operations of data exchange between threads
    const size_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;
    return 0;
}

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

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C++ 98.0%
  • C 1.4%
  • CMake 0.5%
  • Perl 0.1%
  • Batchfile 0.0%
  • Makefile 0.0%