在 C++ 中使用非虚拟公共接口和作用域锁避免死锁
我遇到了一个令我困扰的问题。 我似乎发现了一种很容易解决的情况,但是如果a)我在编程时注意力不集中,或者b)其他人开始实现我的接口并且不知道如何处理,则可能会导致问题这个情况。
这是我的基本设置:
我有一个抽象类,我将其用作多种数据类型的通用接口。 我采用了非虚拟公共接口范例(Sutter,2001)以及范围锁定来提供一定的线程安全性。 示例接口类看起来像这样(我省略了有关作用域锁定和互斥锁实现的详细信息,因为我认为它们不相关):
class Foo
{
public:
A( )
{
ScopedLock lock( mutex );
aImp( );
}
B( )
{
ScopedLock lock( mutex );
bImp( );
}
protected:
aImp( ) = 0;
bImp( ) = 0;
}
然后由用户来实现 aImp 和 bImp,其中这就是问题所在。如果 aImp 执行一些使用 bImp 的操作,那么执行此操作非常容易(并且在某种意义上几乎符合逻辑):
class Bar
{
protected:
aImp( )
{
...
B( );
...
}
bImp( )
{
...
}
}
死锁。 当然,对此的简单解决方案是始终调用受保护的虚拟函数而不是其公共变体(在上面的代码片段中将 B( ) 替换为 bImp( ) )。 但如果我犯了错误,上吊自杀似乎还是太容易了,或者更糟糕的是允许别人上吊自杀。
是否有人有某种方法尝试阻止抽象类的实现者在编译时调用这些公共函数,或者以其他方式帮助避免死锁解决方案?
只是为了好玩,一些互斥体允许进行避免死锁问题的操作。 例如,如果我使用 Windows 函数 EnterCriticalSection 和 LeaveCriticalSection 实现此功能,则没有问题。 但我宁愿避免平台特定的功能。 我目前在范围锁实现中使用 boost::mutex 和 boost::shared_mutex ,据我所知,它并没有尝试避免死锁(我认为我更喜欢这种方式)。
I've run into a problem which seems troubling to me. It seems I've found a situation that's easy enough to work-around, but that could lead to problems if a) I have a lapse in concentration while programming or b) somebody else starts implementing my interfaces and doesn't know how to handle this situation.
Here's my basic setup:
I've got an abstract class that I'm using as a generic interface to several data types. I've adopted the non-virtual public interface paradigm (Sutter, 2001) along with scoped locking to provide some thread safety. An example interface class would look something like this (I've left out details about scoped locking and the mutex implementation, as I don't think they're relevant):
class Foo
{
public:
A( )
{
ScopedLock lock( mutex );
aImp( );
}
B( )
{
ScopedLock lock( mutex );
bImp( );
}
protected:
aImp( ) = 0;
bImp( ) = 0;
}
It is then up to the user to implement aImp and bImp, which is where the problem comes in. If aImp performs some operation which uses bImp, it's extremely easy (and almost logical, in some sense) to do this:
class Bar
{
protected:
aImp( )
{
...
B( );
...
}
bImp( )
{
...
}
}
Deadlock. Of course, the easy solution to this is to always call the protected virtual functions rather than their public variants (replace B( ) with bImp( ) in the above snippet). But it still seems far to easy to hang myself if I make a mistake, or worse yet allow others to hang themselves.
Does anybody have some way to attempt to either stop an implementer of the abstract class from calling those public functions at compile-time, or otherwise help to avoid the deadlock solution?
Just for kicks, some mutexes allow for operation which will avoid deadlock problems. As an example, if I implement this using the windows functions EnterCriticalSection and LeaveCriticalSection, there's no issue. But I'd rather avoid platform specific functionality. I'm currently using boost::mutex and boost::shared_mutex in my scoped lock implementation, and as far as I've seen it doesn't attempt to avoid deadlock (which I think I almost prefer).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
使用私有继承可能会解决您的问题:
私有地从 Foo 派生,然后使用 FooMiddle 确保 Bar 无法访问 A 或 B。但是,bar 仍然能够覆盖 aImp 和 bImp,并且 FooMiddle 中的 using 声明意味着这些仍然可以从 Bar 调用。
或者,可以使用 Pimpl 模式来帮助但不能解决问题。 您最终会得到如下结果:
好处是,在从 FooImpl 派生的类中,它们不再具有“Foo”对象,因此不能轻松调用“A”或“B”。
Using private inheritance will potentially solve your problem:
Deriving from Foo privately, and then using FooMiddle ensures that Bar doesn't have access to A or B. However, bar is still able to override aImp and bImp, and the using declarations in FooMiddle mean that these can still be called from Bar.
Alternatively, an option that will help but not solve the problem is to use the Pimpl pattern. You'd end up with something as follows:
The benefit is that in the classes deriving from FooImpl, they no longer have a "Foo" object and so cannot easily call "A" or "B".
您的互斥体不能是递归互斥体。 如果它不是递归互斥体,则在同一线程中第二次尝试锁定互斥体将导致该线程阻塞。 由于该线程锁定了互斥体,但在该互斥体上被阻止,因此出现了死锁。
您可能想查看:
http://www.boost .org/doc/libs/1_32_0/doc/html/recursive_mutex.html
它应该跨平台实现递归互斥行为。注意Win32 CRITICAL_SECTION(通过 Enter/LeaveCriticalSection 使用)是递归的,这将创建您描述的行为。
Your mutex must not be a recursive mutex. If its not a recursive mutex, a second attempt to lock the mutex in the same thread will result in that thread blocking. Since that thread locked the mutex, but is blocked on that mutex, you have a deadlock.
You probably want to look at:
http://www.boost.org/doc/libs/1_32_0/doc/html/recursive_mutex.html
It's supposed to implement recursive mutex behavior cross platform.Note Win32 CRITICAL_SECTION's (used via Enter/LeaveCriticalSection) are recursive, which would create the behavior you describe.
虽然递归锁可以解决您的问题,但我一直认为,虽然有时是必要的,但在许多情况下,递归锁被用作一种简单的出路,锁定太多了。
您发布的代码显然是为了演示目的而简化的,所以我不确定它是否适用。
举个例子,假设使用资源 X 不是线程安全的。 你有类似的东西。
显然,这会导致僵局。
然而,使用更窄的锁可以消除这个问题。 在尽可能小的范围内使用锁始终是一个好主意,这既是出于性能原因,也是为了避免死锁。
你明白了。
我知道这并不总是可能的(或者会导致代码效率极低),如果不了解更多细节,我不知道它是否适用于您的问题。 但认为无论如何还是值得发布的。
While a recursive lock would solve your problem, I've always felt that, while sometimes necessary, in many situations a recursive lock is used as an easy way out, locking way too much.
Your posted code is obviously simplified for demonstration purposes, so I'm not sure if it'll apply.
As an example, let's say using resource X is not threadsafe. You've got something like.
Obviously, this would result in a deadlock.
Using your locks much narrower however, would eliminate the problem. Using locks in an as small scope as possible is always a good idea, both for performance reasons, as deadlock avoidance.
You get the idea.
I'm aware this is not always possible (or would lead to horribly innefficient code), without knowing more details I don't know if it applies to your problem. But thought it was worth posting anyway.