使整个方法线程安全的最简单方法是什么?

时间:2011-07-29 22:00:36

标签: .net multithreading reentrancy

关于多线程编程似乎有很多东西需要学习,而且它有点令人生畏。

对于我目前的需求,我只想防止在完成之前从另一个线程再次调用的方法,我的问题是:

这是一种使方法成为线程安全的适当(安全)方法吗?

class Foo
{
    bool doingWork;
    void DoWork()
    {
        if (doingWork)  // <- sophistocated thread-safety
            return;     // <-

        doingWork = true;

        try
        {
            [do work here]
        }
        finally
        {
            doingWork = false;
        }
    }
}

如果这还不够,最简单的方法是什么?


编辑:有关该方案的更多信息:

  • 只有一个Foo

  • 的实例
  • 将从Elapsed上的ThreadPool线程调用Foo.DoWork() System.Timers.Timer的事件。

  • 通常Foo.DoWork()会在下一次之前完成 叫,但我想编码它运行时间很长的机会, 并在完成之前再次打电话。


(我也不够聪明,不能确定这个问题是否可以被标记为与语言无关,所以我没有。开明的读者,如果适用,请随时这样做。)

4 个答案:

答案 0 :(得分:8)

您的代码不是线程安全的。您应该使用lock关键字。

在您当前的代码中:

  if (doingWork)
        return;

  // A thread having entered the function was suspended here by the scheduler.

  doingWork = true;

当下一个线程通过时,它也将进入该功能。

这就是应该使用lock结构的原因。它基本上与您的代码相同,但没有线程在中间被中断的风险:

class Foo
{
    object lockObject = new object;
    void DoWork()
    {
        lock(lockObject)
        {
            [do work here]
        }
    }
}

请注意,此代码的语义与原始代码略有不同。此代码将导致第二个线程进入等待然后执行工作。您的原始代码使第二个线程中止。为了更接近原始代码,不能使用C#lock语句。必须直接使用基础Monitor构造:

class Foo
{
    object lockObject = new object;
    void DoWork()
    {
        if(Monitor.TryEnter(lockObject))
        {
            try
            {
                [do work here]
            }
            finally
            {
                Monitor.Exit(lockObject);
            }
        }
    }
}

答案 1 :(得分:3)

Re-entrancy与多线程无关。

重入方法是一种最终可以在同一线程内从其自身内部调用的方法 例如,如果方法引发事件,并且处理该事件的客户端代码在事件处理程序内再次调用该方法,则该方法是可重入的。 保护该方法免于重入意味着确保如果你从内部调用它,它将不会做任何事情或抛出异常。

只要所有内容都位于同一个线程中,您的代码就会在同一个对象实例中受到保护而不再重入。

除非[do work here]能够运行外部代码(例如,通过引发事件,或通过从其他方式调用委托或方法),否则它首先不会重入。

您编辑的问题表明整个部分与您无关 无论如何你应该读它。


您可能(编辑:是)正在寻找排他性 - 确保在多个线程同时调用时,该方法不会一次运行两次。
您的代码不是唯一的。如果两个线程同时运行该方法,并且它们都同时运行if语句,它们都将超过if,然后都设置doingWork标志,并且都将运行整个方法。

为此,请使用lock关键字。

答案 2 :(得分:2)

如果你想要简单代码并且不太关心性能,那就可以像

一样简单
class Foo
{
    bool doingWork;
object m_lock=new object();
    void DoWork()
    {
        lock(m_lock) // <- not sophistocated multithread protection
{
        if (doingWork)  
            return;     
         doingWork = true;
}


        try
        {
            [do work here]
        }
        finally
        {
lock(m_lock) //<- not sophistocated multithread protection
{
            doingWork = false;
}
        }
    }

}

如果你想稍微封装一下锁,你可以像这样创建一个线程安全的属性:

public bool DoingWork
{
get{ lock(m_Lock){ return doingWork;}}
set{lock(m_lock){doingWork=value;}}
}

现在你可以使用它代替字段,但是它会导致更多的时间用于锁定导致锁定使用次数增加。

或者您可以使用全围栏方法(来自优秀的线程书Joseph Albahari online threading

class Foo
{
  int _answer;
  bool _complete;

  void A()
  {
    _answer = 123;
    Thread.MemoryBarrier();    // Barrier 1
    _complete = true;
    Thread.MemoryBarrier();    // Barrier 2
  }

  void B()
  {
    Thread.MemoryBarrier();    // Barrier 3
    if (_complete)
    {
      Thread.MemoryBarrier();       // Barrier 4
      Console.WriteLine (_answer);
    }
  }
}

他说完全围栏比锁定声明快2倍。在某些情况下,您可以通过删除对MemoryBarrier()的不必要调用来提高性能,但使用lock更简单,更清晰且更不容易出错。

我相信这也可以使用基于int的doneWork字段的Interlocked类来完成。

答案 3 :(得分:0)

http://msdn.microsoft.com/en-us/library/system.threading.barrier.aspx

可能希望调查使用屏障,它会为您完成所有工作。这是控制重入代码的标准方法。还允许您一次控制执行该工作的线程数量(如果允许超过1)。