C ++ getter / setter,互斥锁,细粒度锁定

时间:2012-02-21 21:02:11

标签: c++ multithreading boost mutex boost-thread

我有一个由多个线程共享的对象,我想锁定单个成员变量,而不锁定整个对象,这样不同的线程可以同时访问不同的成员变量。 阅读完一些文章后,我使用shared_mutex和getter()/ setter()函数编写代码。

    class Test
    {
    public:
    **// variable, shared_mutex and getter/setter for x**
    double x;
    boost::shared_mutex x_mutex;
    double x_getter();
    void x_setter();
    **// variable, shared_mutex and getter/setter for y**
    ......
    **// variable, shared_mutex and getter/setter for z**
    ......
    };

    double Test::x_getter()
    {
      // get shared access
      boost::shared_lock lock(_access);
      return x;
    }

    void Test::x_setter()
    {
      // get exclusive access
      boost::unique_lock lock(_access);
      // do something with x;
    }

    //getter/setter functions for y and z. 
    ......

代码看起来很笨拙,特别是当成员变量的数量增加时。我想知道这类问题是否有更好的解决方案。

感谢。

3 个答案:

答案 0 :(得分:7)

由于您显然只需要在实际读取/写入数据的短时间内进行锁定,因此您可以将受控数据封装到一个类型中,然后将其用作成员变量:

// note: you probably should add constructors as well
template<typename T> struct synchronized
{
public:
  synchronized& operator=(T const& newval)
  {
    boost::unique_lock lock(mutex);
    value = newval;
  }
  operator T() const
  {
    boost::unique_lock lock(mutex);
    return value;
  }
private:
  T value;
  boost::shared_mutex mutex;
};

class Test
{
public:
  synchronized<double> x;
  synchronized<int> y;
  synchronized<std::string> z;
};

void foo(Test& t)
{
  double read = t.x; // locked, via synchronized<double>::operator double() const
  t.x = 3.14;        // locked, via synchronized<double>::operator=
}

答案 1 :(得分:1)

这是正确的,这种方法看起来很笨拙,很快变得无法管理。因此,我试图通过破坏数据依赖来模拟多线程问题。但是,如果没有进一步了解您要解决的问题,我无法建议如何对问题进行建模。

如果你已经投资了这样的架构而且改变太晚了,那么我会考虑这个。

template<class T>
class SharedValiable
{
    private:
        T                    myT;
        boost::shared_mutex  myTMutex;

    public:
        //
        // Implement appropriate copy, assign and default 
        // to ensure proper value semantics
        //

        T getter() const
        {
            boost::shared_lock lock(_access);
            return x;
        }

        void setter()
        {
            boost::unique_lock lock(_access);
        }
}

这允许每个变量按照您最初的预期进行保护,但可以更轻松地从类中添加新成员或从中删除成员。此外,模板可以专用于某些类型,可以使用原子操作系统操作,如int。 e.g:

template<int>
class SharedValiable
{
    private:
        T                    myT;

    public:
        //
        // Implement appropriate copy, assign and default 
        // to ensure proper value semantics
        //

        T getter() const
        {
            // no need to lock, updates are atomic
            return x;
        }

        void setter()
        {
            // no mutex needed we will use an atomic OS op to update
            InterlockedCompareAndExchange(myT, newVal);
        }
}

答案 2 :(得分:0)

总的来说,我会反对这一点。通常,您正在尝试使用类来确保在进入和离开方法时对对象状态进行某种保证。例如,在列表对象中,您需要链接处于一致状态,您需要计数是正确的等等。仅锁定单个变量很少允许这种情况发生。如果更改链接而不锁定计数状态,则在更新过程中由另一个线程查询时,您会说谎一个或另一个。更糟糕的是,您可以同时更改两个链接,最终会出现一个不再有效的错误列表,具体取决于您的存储空间。当然,这可能是一个例子,但你应该明白这一点。

如果您希望多个线程能够查看对象的状态,那么您需要在整个对象上锁定读取器/写入器,这样可以允许任意数量的读取器,但不允许它们看到对象中间更新。