Why is not used Execute-Around Pointer Idiom in Boost-library as smart pointer for thread-safe access to object?
As known there is Execute-Around Pointer Idiom: http://ift.tt/2a3WFfM
We can use this idiom something like as smart pointer which in addition locks mutex before we get accessing to member variable or function and unlocks mutex after. This doing always, and always locks only mutex which relates to this object.
#include <iostream>
#include <thread>
#include <mutex>
#include <memory>
#include <vector>
#include <numeric>
#include <algorithm>
template<typename T, typename mutex_type = std::recursive_mutex>
class execute_around {
std::shared_ptr<mutex_type> mtx;
std::shared_ptr<T> p;
void lock() const { mtx->lock(); }
void unlock() const { mtx->unlock(); }
public:
class proxy {
std::unique_lock<mutex_type> lock;
T *const p;
public:
proxy (T * const _p, mutex_type& _mtx) : p(_p), lock(_mtx) {}
T* operator -> () {return p;}
const T* operator -> () const {return p;}
};
template<typename ...Args>
execute_around (Args ... args) :
p(std::make_shared<T>(args...)), mtx(std::make_shared<mutex_type>()) {}
proxy operator -> () { return proxy(p.get(), *mtx); }
const proxy operator -> () const { return proxy(p.get(), *mtx); }
template<class... Args> friend class std::lock_guard;
};
void thread_func(execute_around<std::vector<int>> vecc)
{
vecc->push_back(100); // thread-safe
int res = std::accumulate(vecc->begin(), vecc->end(), 0); // thread-safe
std::cout << std::string("res = " + std::to_string(res) + "\n");
{ //all the following code in this scope is thread safe
std::lock_guard<decltype(vecc)> lock(vecc);
auto it = std::find(vecc->begin(), vecc->end(), 100);
if(it != vecc->end()) std::cout << *it << std::endl;
}
}
int main()
{
execute_around<std::vector<int>> vecc(10, 10);
auto copied_vecc_ptr = vecc; // copy-constructor
std::thread t1([&]() { thread_func(copied_vecc_ptr); });
std::thread t2([&]() { thread_func(copied_vecc_ptr); });
t1.join(); t2.join();
return 0;
}
Output:
res = 200
100
res = 300
100
We can use execute_around for any types, any mutexes, and any locks if it added as friend, with several features:
- you can't get access to member of object without locking mutex - is done automatically
- you will not forget to unlock the mutex after accessing
- you can not choosing a wrong mutex which protects another object or section of code
- if you know that this object can be used from multiple threads, then that no one should ever access it without locking the mutex - and
execute_aroundguarantees this (but in addition you can use it together with other mutexes which protect whole code section, not only one object) - you can pass members of object as parameters to the function and this will thread-safe during the entire execution of the function - as we done for
std::accumulate() - we do not get the deadlock when we get accessing multiple members (variables and functions) and do multiple locks in a single expression - if we use
std::recursive_mutex - it hasn't
operator *then you can't get thread-unsafe reference to object, but can get unsafe reference to members of object - it has copy-constructor, but hasn't assignment-
operator = - it may has many copies which pointed to the single object & mutex
Possible problems
In some cases we should use executive_around as standard std::mutex i.e. use lock_guard, but if we forgot this (std::lock_guard<decltype(vecc)> lock(vecc);), then we get a problem:
- we can get reference to member of object, and later use it thread-unsafe
- we can get iterators of this object, and later use it thread-unsafe, also it can be invalidated by other threads
Is there any additional possible problems explaining why is not used Execute-Around Idiom in Boost as smart pointer for thread-safe access to object?
I.e. what are other problems that executive_around has, but standard mutexes and locks hasn't these problems?
Aucun commentaire:
Enregistrer un commentaire