【问题标题】:C++ getter / setter, mutex, fine grained lockingC++ getter/setter、互斥锁、细粒度锁定
【发布时间】:2012-02-21 21:02:11
【问题描述】:

我有一个由多个线程共享的对象,我想锁定单个成员变量,而不锁定整个对象,以便不同的线程可以同时访问不同的成员变量。 在阅读了一些文章之后,我使用 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. 
    ......

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

谢谢。

【问题讨论】:

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


    【解决方案1】:

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

    // 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=
    }
    

    【讨论】:

    • 谢谢你,celtschk,我实现了你的方法,代码看起来更干净了。
    【解决方案2】:

    你说得对,这种方法看起来确实很笨拙,很快就会变得难以管理。因此,我尝试通过打破数据依赖性来模拟多线程问题。但是,如果没有您要解决的问题的进一步背景,我无法建议如何对问题进行建模。

    如果您已经投资于这样的架构并且为时已晚,那么我会考虑这个。

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

    这允许每个变量按照您最初的意图受到保护,但更容易从类中添加新成员或删除成员。此外,模板可以专门用于可以使用原子操作系统操作(如整数)的某些类型。例如:

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

    【讨论】:

      【解决方案3】:

      一般来说,我建议不要这样做。通常,您对类所做的事情是确保在进入和离开方法时对对象的状态有某种保证。例如,在列表对象中,您需要链接处于一致状态,您需要计数正确等等。仅锁定单个变量很少允许这种情况发生。如果您更改链接而没有锁定计数状态,那么当更新中间的不同线程查询它时,您就是在说谎。更糟糕的是,您可以同时更改两个链接,最终得到一个不再有效的混乱列表,具体取决于您的存储空间。当然,这是可能的例子之一,但你应该明白这一点。

      如果您希望多个线程能够查看对象的状态,那么您需要对整个对象进行读取器/写入器锁定,这将允许尽可能多的读取器,但不允许他们查看对象更新中。

      【讨论】:

      • 虽然我普遍同意,但有理由拥有公共数据,这本身不是线程安全的,这更适合作为评论。
      • 对于顶部的谩骂,你可能是对的。答案中真正的答案是使用读/写锁。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-02-20
      • 1970-01-01
      • 2020-05-20
      • 2022-07-31
      • 1970-01-01
      • 1970-01-01
      • 2014-05-24
      相关资源
      最近更新 更多