【问题标题】:Thread-safe replace for code?代码的线程安全替换?
【发布时间】:2012-01-30 08:48:14
【问题描述】:

在开发过程中,我经常会遇到下一个问题:如果某个方法已经被一个线程执行,那么方法一定不能被另一个线程执行。另一个线程必须什么都不做 - 简单地退出方法,因为它我不能使用“锁”。通常,我是这样解决这个问题的:

private bool _isSomeMethodExecuted = false;

public void SomeMethod ()
{
 if (!this._isSomeMethodExecuted) //check if method is already executed
 {
        this._isSomeMethodExecuted = true;

        //Main code of method

        this._isSomeMethodExecuted = false;
 }
}

但是这段代码不是线程安全的:如果一个线程执行条件语句但它在设置标志为真之前停止并且另一个线程可以执行条件 - 那么两个线程都在方法代码中。

是否有任何线程安全的替换?

【问题讨论】:

  • 为什么不能使用lock语句?
  • @Axarydax: IMO 是因为方法执行器不应该被锁定,它应该简单地退出这个方法并继续下面的代码

标签: c# .net multithreading thread-safety


【解决方案1】:

以下是线程安全的,如果方法已经在执行,则不会阻塞 - 即使它已经在同一个线程上执行......这为所有场景提供了重入保护。

private long _isSomeMethodExecuted = 0;

public void SomeMethod ()
{
 if (Interlocked.Increment (ref this._isSomeMethodExecuted) == 1) //check if method is already executed
 {
        //Main code of method

 }
Interlocked.Decrement (ref this._isSomeMethodExecuted);
}

参考见http://msdn.microsoft.com/en-us/library/zs86dyzy.aspx

【讨论】:

  • 递增和比较之间存在差距 - 这就像我的变体。线程可能会在递增之后但在比较之前停止。还是我错了?
  • 不,Interlocked 类以原子方式进行。它可能映射到CHECK AND SET 汇编指令(但我对此不太确定)。
  • @Praetor12 理论上的差距不会改变结果,因为您没有访问变量本身,而是方法的结果和递增/递减本身根据 MSDN 自动发生!您的方法不会以原子方式执行此操作,这意味着它不是线程安全的。
  • 谢谢!这就是我需要的。我知道联锁函数,但我认为我比较的不是变量而是它的复制值(增量方法的结果)。
【解决方案2】:

Monitor 为您完成这项工作,但锁是线程范围的(因此对递归调用开放!)。 lock 语句也使用Monitor(使用阻塞Enter 方法),但您可以使用TryEnter 方法代替:

    if(Monitor.TryEnter(myLockObject))
    {
       try
       {
           DoSomething(); // main code
       }
       finally
       {
           Monitor.Exit(myLockObject);
       }
    }

TryEnter 不会阻塞,而是返回一个bool,表示锁是否成功获取。

如果您希望递归调用不再进入主代码块,则应使用信号量。信号量使用计数器而不是锁定对象,因此即使从同一个线程也无法重新进入:

class Program
{
    private static Semaphore sem = new Semaphore(1, 1);

    static void Main(string[] args)
    {
        MyMethod();
        MyMethod();
    }

    private static void MyMethod()
    {
        if(sem.WaitOne(0))
        {
            try
            {
                Console.WriteLine("Entered.");
                MyMethod(); // recursive calls won't re-enter
            }
            finally
            {
                sem.Release();
            }
        }
        else
        {
            Console.WriteLine("Not entered.");
        }
    }
}

【讨论】:

  • 根据 MSDN,Monitor apporach 的工作方式是在同一个线程已经获得锁的情况下会成功(即它允许多个 Enter/TryEnter 没有 Exit来自同一个线程!)-根据场景可能可以或不可以......
  • 如果我理解正确 - 如果我从这个方法调用方法(递归) - 方法会被执行吗?我不需要它...
  • 是的,确实如此。递归调用也将进入主代码块,这是真的。这是期望的行为吗,@Praetor12?
  • @Praetor12 如果您从自身调用该方法,它将再次执行......并且您需要调用Exit(上述代码中缺少)否则该方法将永远无法从任何其他调用再次线程......
  • @Mudu 的Exit 调用必须与成功的Enter/TryEnter 平衡。在您的代码中,Exit 必须发生在调用 DoSomething 的同一块中(直接在离开该块之前!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-13
  • 1970-01-01
  • 1970-01-01
  • 2013-04-14
  • 2015-11-03
  • 2014-06-16
相关资源
最近更新 更多