防止重入并确保为某些操作获取锁的正确方法是啥?
Posted
技术标签:
【中文标题】防止重入并确保为某些操作获取锁的正确方法是啥?【英文标题】:What is the correct way to prevent reentrancy and ensure a lock is acquired for certain operations?防止重入并确保为某些操作获取锁的正确方法是什么? 【发布时间】:2013-10-01 17:37:22 【问题描述】:我正在设计一个基类,当它被继承时,它将针对多线程环境中的上下文提供业务功能。每个实例都可能有长时间运行的初始化操作,所以我想让对象可重用。为此,我需要能够:
-
为这些对象之一分配上下文以允许其工作
防止已为对象分配新上下文
防止在对象没有上下文时访问某些成员
此外,每个上下文对象可以被许多工作对象共享。
是否有适合我想要做的正确同步原语?这是我想出的最适合我需要的模式:
private Context currentContext;
internal void BeginProcess(Context currentContext)
// attempt to acquire a lock; throw if the lock is already acquired,
// otherwise store the current context in the instance field
internal void EndProcess()
// release the lock and set the instance field to null
private void ThrowIfNotProcessing()
// throw if this method is called while there is no lock acquired
使用上述方法,我可以保护不应访问的基类属性和方法,除非对象当前处于处理状态。
protected Context CurrentContext
get
this.ThrowIfNotProcessing();
return this.context;
protected void SomeAction()
this.ThrowIfNotProcessing();
// do something important
虽然我最初是使用Monitor.Enter
和相关函数,但这并不能阻止同线程重入(在原始线程上多次调用BeginProcess
)。
【问题讨论】:
既然您已经throw if the lock is already acquired
并且不想等待当前处理完成:为什么不只是throw if current context is not null
?
【参考方案1】:
.NET 中有一个不可重入的同步对象,您正在寻找一个信号量。
在你承诺之前,一定要把你的鸭子排成一排,问问自己,BeginProcess() 怎么可能在同一个线程上再次被调用。这是非常非常不寻常的,您的代码必须可重入才能发生。这通常只会发生在具有调度程序循环的线程上,GUI 应用程序的 UI 线程就是一个常见的例子。如果这确实可行并且您实际上使用了信号量,那么您也将处理后果,您的代码将死锁。因为它递归到 BeginProcess 并在信号量上停止。因此永远不会完成,也永远无法调用 EndProcess()。 Monitor 和 Mutex 可重入是有充分理由的 :)
【讨论】:
你提出了好点;其主要功能是确保调用BeginProcess
的基础架构代码正确执行。 “一个好的程序员在穿过一条单行道之前会左右两边看。”我不会让信号量阻塞(我会以零超时调用WaitOne
)并检查返回值以决定是否抛出。这将防止出现死锁情况。
这个答案有经验!【参考方案2】:
您可以使用 .NET Framework 2.0 附带的 Semaphore
类。
信号量的一个很好的用途是同步有限数量的资源。在您的情况下,您似乎拥有想要在消费者之间共享的资源,例如 Context
。
您可以创建一个信号量来管理以下资源:
var resourceManager = new Semaphore(0, 10);
然后在BeginProcess
方法中等待资源可用:
resourceManager.WaitOne();
最后使用 EndProcess
方法释放资源:
resourceManager.Release();
这是一个good blog,关于在像您这样的情况下使用信号量:
https://web.archive.org/web/20121207180440/http://www.dijksterhuis.org/using-semaphores-in-c/
【讨论】:
您提供的链接已损坏!【参考方案3】:Interlocked
类可用于退出方法的线程安全解决方案,而不是在进行重入调用时进行阻塞。像 Vlad Gonchar 解决方案,但线程安全。
private int refreshCount = 0;
private void Refresh()
if (Interlocked.Increment(ref refreshCount) != 1) return;
try
// do something here
finally
Interlocked.Decrement(ref refreshCount);
【讨论】:
【参考方案4】:有一种非常简单的方法可以防止重入(在一个线程上):
private bool bRefresh = false;
private void Refresh()
if (bRefresh) return;
bRefresh = true;
try
// do something here
finally
bRefresh = false;
【讨论】:
这绝对不会阻止重入。 它不会阻止它,因为if (bRefresh) return;
和bRefresh = true;
不是原子操作,所以当bRefresh 为false
时,多个线程可以评估条件,跳过return
然后继续将其设置为 true
并完成剩下的工作。
@Mike 当然它确实 防止重新进入——它只是不是线程安全的。这两个概念完全不相关——访问同一资源的两个线程与重入无关。如果您需要防止来自多个线程的重入和同时访问,则需要结合这两种方法或使用同时考虑这两种方法的原语。以上是关于防止重入并确保为某些操作获取锁的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章