一度に1つのスレッドだけがC++クラスのメソッドを実行できるようにしたいです。つまり、クラスをモニターのように動作させます。
これを行うためのパターン、テンプレート化された方法、または使用できるBoostクラスはありますか?これまでの私の唯一のアイデアは、クリティカルセクションのメンバーを追加し、各メソッドの最初にそれを取得し、最後にリリースすることです(もちろん、RAIIを使用)。しかし、それは非常に冗長であるように思われ、他のクラスで再利用することはできません。
一度に1つのスレッドだけがC++クラスのメソッドを実行できるようにしたいです。つまり、クラスをモニターのように動作させます。
これを行うためのパターン、テンプレート化された方法、または使用できるBoostクラスはありますか?これまでの私の唯一のアイデアは、クリティカルセクションのメンバーを追加し、各メソッドの最初にそれを取得し、最後にリリースすることです(もちろん、RAIIを使用)。しかし、それは非常に冗長であるように思われ、他のクラスで再利用することはできません。
operator->
これは、以前に受け入れられた答えよりもはるかにクリーンな構文を提供する最新のc++を慎重に使用することで実現できます。
template<class T>
class monitor
{
public:
template<typename ...Args>
monitor(Args&&... args) : m_cl(std::forward<Args>(args)...){}
struct monitor_helper
{
monitor_helper(monitor* mon) : m_mon(mon), m_ul(mon->m_lock) {}
T* operator->() { return &m_mon->m_cl;}
monitor* m_mon;
std::unique_lock<std::mutex> m_ul;
};
monitor_helper operator->() { return monitor_helper(this); }
monitor_helper ManuallyLock() { return monitor_helper(this); }
T& GetThreadUnsafeAccess() { return m_cl; }
private:
T m_cl;
std::mutex m_lock;
};
アイデアは、矢印演算子を使用して基になるオブジェクトにアクセスすることですが、それは、関数呼び出しの周りのミューテックスをロックしてからロック解除するヘルパーオブジェクトを返します。次に、言語の魔法を繰り返し適用operator->
することで、基になるオブジェクトへの参照を取得します。
使用法:
monitor<std::vector<int>> threadSafeVector {5};
threadSafeVector->push_back(0);
threadSafeVector->push_back(1);
threadSafeVector->push_back(2);
// Create a bunch of threads that hammer the vector
std::vector<std::thread> threads;
for(int i=0; i<16; ++i)
{
threads.push_back(std::thread([&]()
{
for(int i=0; i<1024; ++i)
{
threadSafeVector->push_back(i);
}
}));
}
// You can explicitely take a lock then call multiple functions
// without the overhead of a relock each time. The 'lock handle'
// destructor will unlock the lock correctly. This is necessary
// if you want a chain of logically connected operations
{
auto lockedHandle = threadSafeVector.ManuallyLock();
if(!lockedHandle->empty())
{
lockedHandle->pop_back();
lockedHandle->push_back(-3);
}
}
for(auto& t : threads)
{
t.join();
}
// And finally access the underlying object in a raw fashion without a lock
// Use with Caution!
std::vector<int>& rawVector = threadSafeVector.GetThreadUnsafeAccess();
rawVector.push_back(555);
// Should be 16393 (5+3+16*1024+1)
std::cout << threadSafeVector->size() << std::endl;
まず、汎用モニタークラスを作成します。C ++ 11のパワーを使用すると、次のように簡単に実行できます。
template <class F>
struct FunctionType;
template <class R, class Object, class... Args>
struct FunctionType<R (Object::*)(Args...)> {
typedef R return_type;
};
template <class R, class Object, class... Args>
struct FunctionType<R (Object::*)(Args...) const> {
typedef R return_type;
};
template <class Object_>
class Monitor {
public:
typedef Object_ object_type;
template <class F, class... Args >
typename FunctionType<F>::return_type operation(const F& f, Args... args)
{
critical_section cs;
return (object.*f)(args...);
}
template <class F, class... Args >
typename FunctionType<F>::return_type operation(const F& f, Args... args) const
{
critical_section cs;
return (object.*f)(args...);
}
private:
object_type object;
class critical_section {};
};
もちろんcritical_section
、実装はあなた次第です。POSIXまたはBOOSTをお勧めします。
今すぐ使用する準備ができています:
Monitor<std::vector<int> > v;
v.operation((void (std::vector<int>::*)(const int&)) &std::vector<int>::push_back, 1);
v.operation((void (std::vector<int>::*)(const int&)) &std::vector<int>::push_back, 2);
size = v.operation(&std::vector<int>::size);
std::cout << size << std::endl;
ご覧のとおり、呼び出すメンバー関数を明示的に指定する必要がある場合があります-std ::vector<>には複数のpush_backがあります...
可変個引数テンプレートをまだサポートしていないコンパイラの場合-以下のテンプレートなしのソリューション-最大2つの引数を指定する時間があります-非常に不便です-必要に応じて-引数を追加して関数を追加します:
template <class F>
struct FunctionType;
template <class R, class Object>
struct FunctionType<R (Object::*)()> {
typedef R return_type;
};
template <class R, class Object>
struct FunctionType<R (Object::*)() const> {
typedef R return_type;
};
template <class R, class Object, class Arg1>
struct FunctionType<R (Object::*)(Arg1)> {
typedef R return_type;
};
template <class R, class Object, class Arg1>
struct FunctionType<R (Object::*)(Arg1) const> {
typedef R return_type;
};
template <class R, class Object, class Arg1, class Arg2>
struct FunctionType<R (Object::*)(Arg1,Arg2)> {
typedef R return_type;
};
template <class R, class Object, class Arg1, class Arg2>
struct FunctionType<R (Object::*)(Arg1,Arg2) const> {
typedef R return_type;
};
template <class Object_>
class Monitor {
public:
typedef Object_ object_type;
template <class F>
typename FunctionType<F>::return_type operation(const F& f)
{
critical_section cs;
return (object.*f)();
}
template <class F>
typename FunctionType<F>::return_type operation(const F& f) const
{
critical_section cs;
return (object.*f)();
}
template <class F, class Arg1>
typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1)
{
critical_section cs;
return (object.*f)(arg1);
}
template <class F, class Arg1>
typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1) const
{
critical_section cs;
return (object.*f)(arg1);
}
template <class F, class Arg1, class Arg2>
typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1, Arg2 arg2)
{
critical_section cs;
return (object.*f)(arg1, arg2);
}
template <class F, class Arg1, class Arg2>
typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1, Arg2 arg2) const
{
critical_section cs;
return (object.*f)(arg1, arg2);
}
private:
object_type object;
class critical_section {};
};