使C++类成为监视器(在并发意义上)

dar*_*mos 15 c++ concurrency multithreading templates boost

我想确保一次只有一个线程可以运行我的C++类的方法.换句话说,让类的行为类似于Monitor.

是否有模式,模板化的方式来做这个,或者我可以使用一些Boost类?因为我到目前为止唯一的想法是添加一个关键部分成员,并在每个方法的开头获取它并在结束时释放它(当然使用RAII).但这似乎非常多余,我不能将它重用于其他类.

Pio*_*ycz 12

首先制作通用监控类.借助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 {};
};
Run Code Online (Sandbox Code Playgroud)

当然,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;
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,有时您需要明确说明要调用的成员函数 - std :: vector <>有多个push_back ...


对于仍然不支持可变参数模板的编译器 - 没有它的解决方案 - 我有时间最多两个参数 - 它非常不方便 - 如果需要 - 添加更多参数的函数:

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 {};
};
Run Code Online (Sandbox Code Playgroud)

  • 我怜悯任何非c ++程序员随机绊倒这个问题. (4认同)

Mik*_*ine 12

你可以通过一些明智的使用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;
};
Run Code Online (Sandbox Code Playgroud)

这个想法是你使用箭头操作符来访问底层对象,但是返回一个帮助对象,它锁定然后解锁函数调用周围的互斥锁.然后通过反复应用语言的魔力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;
Run Code Online (Sandbox Code Playgroud)

  • @普拉纳夫克。不,不会。尝试一下 - 设置一个调试器并在 `lock_guard` 和函数调用的析构函数中放置一个断点。只要返回指针然后解锁它_before_它调用该函数,它将需要锁定。这使用了这样一个事实,即在完整表达式 _after_ 函数调用(然后解锁锁)结束时,临时对象被销毁。 (3认同)
  • 我发现很难相信 `threadSafeVector-&gt;push_back(0);` 可以工作,即使在内部,我们调用了 `operator-&gt;` 两次,这让我认为它应该像这样工作: `threadSafeVector-&gt;-&gt;推回(0);`。第一个“threadSafeVector-&gt;”返回一个monitor_helper,然后第二个“-&gt;”在此监视器助手上调用operator-&gt;。 (2认同)
  • @pranavk c ++中的arrow运算符具有特殊的语义,当上一个调用的返回类型是此答案使用的指针时,可以重复应用它。有关详细信息,请参见示例[this](/sf/ask/747446311/)。关于第二点,我们需要在函数调用期间保持锁定,这是特殊的`monitor_helper`结构的生存期。用普通锁是不可能的。 (2认同)