为什么传递对 Mutex class 的引用不是一个好的设计?
Why is passing references to the Mutex class not a good design?
从这里开始:
the way you pass around references(!) to your mutex class is plainly asking for trouble, it defies any kind of encapsulation.
为什么这是个问题?我应该按值传递然后编写复制构造函数吗?
在这种情况下,缺少封装会造成什么危害?我应该如何封装什么?
此外,
Passing a reference to the lock is a bad idea -- you don't "use" the lock, you only acquire and then you give it back. Moving it around makes it hard to track your use of the (critical) resource. Passing a reference to a mutex variable rather than a lock is maybe not so bad, but still it makes it harder to know what parts of the program may deadlock, so its something to avoid.
请用简单的语言举例说明 - 为什么传递引用是个坏主意?
我认为这是糟糕的抽象和糟糕的封装。 mutex
通常是默认构造的,删除了复制构造函数,具有多个引用同一逻辑对象的互斥对象很容易出错,即它可能导致死锁和其他竞争条件,因为程序员或 reader可以假设它们是不同的实体。
此外,通过指定您正在使用的内部互斥锁,您将公开线程的实现细节,从而破坏互斥锁的抽象 class。如果您使用的是 pthread_mutex_t
,那么您很可能会使用内核线程 (pthreads)。
封装也被破坏,因为您的 Mutex 不是一个单独的封装实体,而是分散到多个(可能是悬挂的)引用中。
如果您想将 pthread_mutex_t
封装到 class 中,您可以这样做
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 对象旨在在实现文件中声明的共享范围内共享,而不是在本地声明并作为引用在函数中传递。理想情况下,您只会将参数传递给您需要的线程构造函数。传递对在与相关函数(在本例中为线程执行)相同 "level" 范围内声明的对象的引用通常会导致代码错误。如果声明互斥锁的范围不再存在会怎样? mutex
的析构函数会使互斥锁的内部实现无效吗?如果互斥量通过传递进入整个其他模块,并且该模块启动自己的线程并认为互斥量永远不会阻塞,会发生什么情况,这可能会导致严重的死锁。
还有一个你想使用互斥移动构造函数的情况是在互斥工厂模式中,如果你想创建一个新的互斥锁,你将进行一个函数调用,该函数将 return mutex,然后您可以将其添加到互斥列表或传递给通过某种共享数据请求它的线程(上述列表对于此共享数据是个好主意)。然而,获得这样一个正确的互斥锁工厂模式可能非常棘手,因为您需要锁定对互斥锁公共列表的访问。尝试应该是一件有趣的事情!
如果作者的意图是避免全局范围,那么在实现文件中将其声明为静态对象一次就足够了。
我将其提炼为单独的问题:
1.什么时候通过引用传递任何对象是合适的?
2. 什么时候共享互斥量合适?
将对象作为参数传递的方式反映了您希望如何在调用者和被调用者之间共享对象的生命周期。如果通过引用传递,则必须假设被调用者将仅在调用期间使用该对象,或者如果引用由被调用者存储,则被调用者的生命周期比引用短。如果处理动态分配的对象,您可能应该使用智能指针,它(除其他外)允许您更明确地传达您的意图(参见本主题的 Herb Sutter's treatment)。
应避免共享互斥量。无论是通过引用还是任何其他方式传递,都是如此。通过共享互斥体,对象允许自身在内部受到外部实体的影响。这违反了基本封装并且是足够的理由。 (请参阅有关 object-oriented 编程的任何文本以了解封装的优点)。共享互斥锁的一个真正后果是死锁的可能性。
举个简单的例子:
- A 拥有互斥量
- A 与 B 共享互斥
- B 在调用 A 上的函数之前获得锁
- A函数尝试获取锁
- 死锁..
从设计的角度来看,为什么要共享互斥量?互斥锁保护可能被多个线程访问的资源。该互斥锁应隐藏(封装)在控制该资源的 class 中。互斥量只是 class 保护资源的一种方式;它的实现细节只有 class 应该知道。相反,共享控制资源的 class 的实例,并允许它以任何它想要的方式确保 thread-safety 在自身内部。
从这里开始:
the way you pass around references(!) to your mutex class is plainly asking for trouble, it defies any kind of encapsulation.
为什么这是个问题?我应该按值传递然后编写复制构造函数吗?
在这种情况下,缺少封装会造成什么危害?我应该如何封装什么?
此外,
Passing a reference to the lock is a bad idea -- you don't "use" the lock, you only acquire and then you give it back. Moving it around makes it hard to track your use of the (critical) resource. Passing a reference to a mutex variable rather than a lock is maybe not so bad, but still it makes it harder to know what parts of the program may deadlock, so its something to avoid.
请用简单的语言举例说明 - 为什么传递引用是个坏主意?
我认为这是糟糕的抽象和糟糕的封装。 mutex
通常是默认构造的,删除了复制构造函数,具有多个引用同一逻辑对象的互斥对象很容易出错,即它可能导致死锁和其他竞争条件,因为程序员或 reader可以假设它们是不同的实体。
此外,通过指定您正在使用的内部互斥锁,您将公开线程的实现细节,从而破坏互斥锁的抽象 class。如果您使用的是 pthread_mutex_t
,那么您很可能会使用内核线程 (pthreads)。
封装也被破坏,因为您的 Mutex 不是一个单独的封装实体,而是分散到多个(可能是悬挂的)引用中。
如果您想将 pthread_mutex_t
封装到 class 中,您可以这样做
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 对象旨在在实现文件中声明的共享范围内共享,而不是在本地声明并作为引用在函数中传递。理想情况下,您只会将参数传递给您需要的线程构造函数。传递对在与相关函数(在本例中为线程执行)相同 "level" 范围内声明的对象的引用通常会导致代码错误。如果声明互斥锁的范围不再存在会怎样? mutex
的析构函数会使互斥锁的内部实现无效吗?如果互斥量通过传递进入整个其他模块,并且该模块启动自己的线程并认为互斥量永远不会阻塞,会发生什么情况,这可能会导致严重的死锁。
还有一个你想使用互斥移动构造函数的情况是在互斥工厂模式中,如果你想创建一个新的互斥锁,你将进行一个函数调用,该函数将 return mutex,然后您可以将其添加到互斥列表或传递给通过某种共享数据请求它的线程(上述列表对于此共享数据是个好主意)。然而,获得这样一个正确的互斥锁工厂模式可能非常棘手,因为您需要锁定对互斥锁公共列表的访问。尝试应该是一件有趣的事情!
如果作者的意图是避免全局范围,那么在实现文件中将其声明为静态对象一次就足够了。
我将其提炼为单独的问题: 1.什么时候通过引用传递任何对象是合适的? 2. 什么时候共享互斥量合适?
将对象作为参数传递的方式反映了您希望如何在调用者和被调用者之间共享对象的生命周期。如果通过引用传递,则必须假设被调用者将仅在调用期间使用该对象,或者如果引用由被调用者存储,则被调用者的生命周期比引用短。如果处理动态分配的对象,您可能应该使用智能指针,它(除其他外)允许您更明确地传达您的意图(参见本主题的 Herb Sutter's treatment)。
应避免共享互斥量。无论是通过引用还是任何其他方式传递,都是如此。通过共享互斥体,对象允许自身在内部受到外部实体的影响。这违反了基本封装并且是足够的理由。 (请参阅有关 object-oriented 编程的任何文本以了解封装的优点)。共享互斥锁的一个真正后果是死锁的可能性。
举个简单的例子:
- A 拥有互斥量
- A 与 B 共享互斥
- B 在调用 A 上的函数之前获得锁
- A函数尝试获取锁
- 死锁..
从设计的角度来看,为什么要共享互斥量?互斥锁保护可能被多个线程访问的资源。该互斥锁应隐藏(封装)在控制该资源的 class 中。互斥量只是 class 保护资源的一种方式;它的实现细节只有 class 应该知道。相反,共享控制资源的 class 的实例,并允许它以任何它想要的方式确保 thread-safety 在自身内部。