为什么传递对Mutex类的引用不是一个好的设计?

时间:2016-01-01 03:07:33

标签: c++ pthreads mutex

从这里开始:Logic error in my defined Mutex class and the way I use it in producer consumer program - pthreads

  

将引用(!)传递给mutex类的方式显然是在寻找麻烦,它无视任何类型的封装。

为什么这是一个问题?我应该通过值传递然后编写复制构造函数吗?

在这种情况下,缺少封装有什么危害?我应该如何封装什么?

此外,Why is passing references to the Mutex class not a good design?

  

传递对锁的引用是一个坏主意 - 你不“使用”锁,你只是获得然后你还给它。移动它使得很难跟踪您对(关键)资源的使用。传递对互斥锁变量而不是锁定的引用可能并不是那么糟糕,但仍然会让人更难以知道程序的哪些部分可能会死锁,所以要避免这种情况。

请用简单的语言解释一下 - 为什么传递引用是一个坏主意?

2 个答案:

答案 0 :(得分:6)

我认为这是糟糕的抽象以及糟糕的封装。 mutex通常默认构造时删除了复制构造函数,具有多个引用同一逻辑对象的互斥对象容易出错,即它可能导致死锁和其他竞争条件,因为程序员或读者可以假设它们是不同的实体。

此外,通过指定您正在使用的内部互斥体,您将公开线程的实现细节,从而破坏Mutex类的抽象。如果您使用的是pthread_mutex_t,那么您很可能会使用内核线程(pthreads)。

封装也被破坏,因为你的互斥锁不是一个封装的实体,而是分散在几个(可能是悬空的)引用中。

如果你想将pthread_mutex_t封装到一个类中,你可以这样做

class Mutex {
public:
    void lock(); 
    void unlock();

    // Default and move constructors are good! 
    // You can store a mutex in STL containers with these
    Mutex();
    Mutex(Mutex&&);
    ~Mutex();

    // These can lead to deadlocks!
    Mutex(const Mutex&) = delete;
    Mutex& operator= (const Mutex&) = delete;
    Mutex& operator= (Mutex&&) = delete;

private:
    pthread_mutex_t internal_mutex;
};

Mutex对象应在实现文件中声明的共享范围内共享,而不是在本地声明它并在函数中作为引用传递。理想情况下,您只需将参数传递给您需要的线程构造函数。将对范围内声明的对象的引用传递到相同的"级别"因为有问题的函数(在这种情况下是线程执行)通常会导致代码错误。如果声明互斥锁的范围不再存在会发生什么? mutex的析构函数是否会使互斥体的内部实现无效?如果互斥体通过传递进入整个其他模块并且该模块启动其自己的线程并且认为互斥锁永远不会阻塞,会发生什么情况,这可能导致令人讨厌的死锁。

另外一个你想使用互斥锁移动构造函数的情况是在互斥锁工厂模式中说,如果你想创建一个新的互斥锁你会进行一个函数调用,那个函数会返回一个你将添加的互斥锁到您的互斥锁列表或传递给通过某种共享数据请求它的线程(前面提到的列表对这个共享数据是一个好主意)。然而,获得这样的互斥工厂模式可能非常棘手,因为您需要锁定对公共互斥体列表的访问。尝试它应该是一件有趣的事情!

如果作者的意图是避免全局范围,那么在实现文件中将其声明为静态对象应该是足够的抽象。

答案 1 :(得分:4)

我将其提炼为单独的问题: 1.何时通过引用传递任何对象是否合适? 2.何时适合共享互斥锁?

  1. 将对象作为参数传递的方式反映了您希望如何在调用者和被调用者之间共享对象的生命周期。如果通过引用传递,则必须假定被调用者仅在调用期间使用对象,或者如果被调用者存储引用,则被调用者的生命周期短于引用。如果处理动态分配的对象,您可能应该使用智能指针,其中(除其他外)允许您更明确地传达您的意图(参见本主题的Herb Sutter's treatment)。

  2. 应避免共享互斥锁。无论是通过引用还是通过任何其他方式传递都是如此。通过共享互斥锁,对象允许自己在外部实体内部受到影响。这违反了基本的封装,是足够的理由。 (有关封装的优点,请参阅面向对象编程的任何文本)。共享互斥锁的一个真正后果是死锁的可能性。

    举个简单的例子:

    • A拥有互斥锁
    • A股互惠与B
    • B在A
    • 上调用函数之前获得锁定
    • A&#39的功能试图获取锁
    • 死锁..
  3. 从设计角度来看,您为什么要共享互斥锁?互斥锁保护可能由多个线程访问的资源。应该将该互斥锁隐藏(封装)在控制该资源的类中。互斥是这个类可以保护资源的一种方式;它的实现细节只有类应该知道。相反,共享控制资源的类的实例,并允许它以任何方式确保自身内部的线程安全。