关于多线程编程似乎有很多东西需要学习,而且它有点令人生畏。
对于我目前的需求,我只想防止在完成之前从另一个线程再次调用的方法,我的问题是:
这是一种使方法成为线程安全的适当(安全)方法吗?
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()会在下一次之前完成 叫,但我想编码它运行时间很长的机会, 并在完成之前再次打电话。
(我也不够聪明,不能确定这个问题是否可以被标记为与语言无关,所以我没有。开明的读者,如果适用,请随时这样做。)
答案 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)。