代码之家  ›  专栏  ›  技术社区  ›  Marcin

互斥体应该是可变的吗?

  •  53
  • Marcin  · 技术社区  · 14 年前

    不确定这是一个风格问题,还是有硬规则的问题…

    如果我希望尽可能保持公共方法接口的常量,但使对象线程安全,我应该使用可变的互斥体吗?一般来说,这是一种好的风格,还是应该首选非常量方法接口?请证明你的观点。

    2 回复  |  直到 12 年前
        1
  •  28
  •   Alexandre C.    14 年前

    [ ] 答案已编辑 ]

    基本上,使用带有可变互斥体的const方法是一个好主意(不要顺便返回引用,确保按值返回),至少表明它们不修改对象。互斥体不应该是常量,将锁定/解锁方法定义为常量将是一个无耻的谎言。

    事实上,这(和记忆化)是我所看到的 mutable 关键字。

    您还可以使用对象外部的互斥体:安排所有方法都是可重入的,并让用户自己管理锁: { lock locker(the_mutex); obj.foo(); } 不是很难打字,而且

    {
        lock locker(the_mutex);
        obj.foo();
        obj.bar(42);
        ...
    }
    

    它的优点是不需要两个互斥锁(并且可以保证对象的状态不会改变)。

        2
  •  49
  •   paercebal    12 年前

    隐藏的问题是:您将互斥体放在哪里来保护您的类?

    作为一个总结,假设您想要读取受互斥保护的对象的内容。

    “read”方法在语义上应该是“const”,因为它不会改变对象本身。但要读取该值,需要锁定互斥体,提取该值,然后解锁互斥体,这意味着必须修改互斥体本身,这意味着互斥体本身不能是“const”。

    如果互斥体是外部的

    然后一切都好了。对象可以是“const”,互斥体不需要是:

    Mutex mutex ;
    
    int foo(const Object & object)
    {
       Lock<Mutex> lock(mutex) ;
       return object.read() ;
    }
    

    imho,这是一个糟糕的解决方案,因为任何人都可以重用互斥体来保护其他东西。包括你。事实上,您将背叛自己,因为如果您的代码足够复杂,您将对这个或那个互斥体到底保护什么感到困惑。

    我知道:我是那个问题的受害者。

    如果互斥体是内部的

    出于封装目的,您应该尽可能地将互斥体放在它所保护的对象附近。

    通常,您将编写一个包含互斥体的类。但迟早,您将需要保护一些复杂的STL结构,或者由另一个内部没有互斥的人编写的任何东西(这是一件好事)。

    这样做的一个好方法是使用继承模板派生原始对象,并添加互斥功能:

    template <typename T>
    class Mutexed : public T
    {
       public :
          Mutexed() : T() {}
          // etc.
    
          void lock()   { this->m_mutex.lock() ; }
          void unlock() { this->m_mutex.unlock() ; } ;
    
       private :
          Mutex m_mutex ;
    }
    

    这样,你就可以写:

    int foo(const Mutexed<Object> & object)
    {
       Lock<Mutexed<Object> > lock(object) ;
       return object.read() ;
    }
    

    问题是它不起作用,因为 object 是const,而lock对象正在调用非const lock unlock 方法。

    困境

    如果你相信 const 只限于位常量对象,然后您就被拧紧了,必须返回到“外部互斥解决方案”。

    解决办法是承认 康斯特 更像是语义限定符 volatile 当用作类的方法限定符时)。你隐瞒了一个事实,那就是这个类不是完全的 康斯特 但是仍然要确保提供一个实现,以保证在调用 康斯特 方法。

    然后,必须声明互斥体可变,以及锁定/解锁方法 康斯特 :

    template <typename T>
    class Mutexed : public T
    {
       public :
          Mutexed() : T() {}
          // etc.
    
          void lock()   const { this->m_mutex.lock() ; }
          void unlock() const { this->m_mutex.unlock() ; } ;
    
       private :
          mutable Mutex m_mutex ;
    }
    

    内部互斥解决方案是一个很好的解决方案:一方面必须声明一个靠近另一个的对象,另一方面将两个对象聚合到包装器中,最终是相同的事情。

    但这种聚合有以下优点:

    1. 更自然(在访问对象之前锁定对象)
    2. 一个对象,一个互斥体。由于代码样式强制您遵循此模式,它降低了死锁风险,因为一个互斥体只保护一个对象(而不是您真正不记得的多个对象),并且一个对象只受一个互斥体保护(而不是受需要在右侧锁定的多个互斥体保护),或者DER)
    3. 上面的互斥类可以用于任何类

    因此,将互斥体尽可能靠近互斥对象(例如,使用上面的互斥构造),然后转到 mutable 互斥体的限定符。

    编辑2013-01-04

    显然,Herb Sutter也有同样的观点:他对 康斯特 可变的 在C++ 11中很有启发性:

    http://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/