2011-07-29 84 views
9

似乎有很多关于多线程编程的知识,它有点吓人。使整个方法线程安全的最简单方法?

对于我目前的需要,我只是想防止的方法从另一个线程再次被称为完成之前,我的问题是:

这是适当的(安全)的方法,使方法线程安全?

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

     doingWork = true; 

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

如果这还不够,什么是实现这一目标的最简单的方法?


编辑:关于方案更多信息:

  • 有富的只有一个实例

  • Foo.DoWork()将被从线程池线程呼吁Elapsed System.Timers.Timer的事件。

  • 通常Foo.DoWork()将在下一次的 称为前完成劫,但我要为渺茫的机会,它会长期来看, 并获得完成之前再次调用代码。


(我还没有聪明到可以肯定,如果这个问题能够标记语言无关,所以我没有。开明的读者,可随时做,如果适用)。

+0

是为每个线程实例化一个Foo类型的对象,还是它跨多个线程共享? – NotMe

+0

提供有关调用dowork方法的代码的更多细节,是否有多个线程? – sll

+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); 
      } 
     } 
    } 
} 
+0

我并不需要放弃,所以锁定方法应该没问题。甜 - 这很简单!谢谢。 –

+0

[瓦伦丁](http://stackoverflow.com/questions/6879468/simplest-way-to-make-a-whole-method-thread-safe/6879698#6879698)有一种方法,只是锁中止( )。对你的评论感兴趣。 –

0

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

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

+0

这似乎过于复杂,我需要。我真的只想实现我的代码片段暗示的内容:重新进入时跳出。 –

+0

检查障碍中的参与者数量,如果它> 0返回,则在最后删除参与者时添加参与者并开始工作。屏障是为了线程安全。 –

+0

你可以做检查并以原子方式添加一个参数吗?如果是这样,怎么样? –

3

重新进入与多线程无关。

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

只要所有内容都在同一个线程中,您的代码就不会在同一个对象实例中重新进入。

除非[do work here]能够运行外部代码(例如,通过引发一个事件,或者通过调用其他代理或方法),它并不是重新进入的。

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


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

为此,请使用lock关键字。

+0

那么它确实说标签中多线程所以人们只能假设。 –

+1

人们只能假设_what_?这个问题与多线程无关。 **编辑**:现在,它确实。 – SLaks

+0

不可否认,我只是在考虑从另一个线程再次调用该方法。谢谢你指出 - 我已经编辑了这个问题。不过,我相信这个术语也可以应用于多线程场景。如果不是的话,当一个方法需要能够应对被另一个线程调用的时候,你会怎样调用它? –

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); 
    } 
    } 
} 

他指出,全围栏2X比lock语句快。在某些情况下,您可以通过删除对MemoryBarrier()的不必要的调用来提高性能,但使用lock很简单,更清晰且不易出错。

我相信这也可以使用基于INT的Interlocked类做基于int的工作领域。

+0

你的lock()(锁定对doingWork的访问)和Anders的lock()方法(锁定对这个工作的代码的访问)有什么区别? –

+0

有逻辑上的区别。正如我从他的回答方法中看到的那样是可重入的,并且进一步的呼叫正在排队。在我的代码中,我假设你不想让其他线程排队这个方法,如果它在一个线程上运行的话。根据我的经验,我的方法通常是首选,例如,当您不想允许用户通过双击按钮启动多个服务器请求时。 PS:他说那不能用锁来完成,我用锁来做。 Monitor.TryEnter可以获得更好的性能,但是对于日常需求,我想可以稍微高级一些,因为id会将其留给关键的性能代码。 –

+0

哦,我看,很酷。你怎么样,安德斯? :) –