2012-08-14 46 views
12

我有一个工作线程中的对象,我可以指示停止运行。我可以实现这个用布尔或的AutoResetEvent:AutoResetEvent与布尔值来停止线程

布尔:

private volatile bool _isRunning; 

public void Run() { 
    while (_isRunning) 
    { 
     doWork(); 
     Thread.Sleep(1000); 
    } 
} 

的AutoResetEvent:

private AutoResetEvent _stop; 

public void Run() { 
    do { 
     doWork(); 
    } while (!_stop.WaitOne(1000)); 
} 

然后Stop()方法将设置_isRunning为false,或致电_stop.Set()

除此之外,AutoResetEvent的解决方案可能会稍微停止一点,这些方法之间有什么区别吗?这个比那个好吗?

+2

我会使用一个计时器,而不是睡觉。要结束它,你可以停止计时器。 – Servy 2012-08-14 13:29:21

+0

我已经添加了一些观察到可能的答案,基本上我不认为这些片段做你想要的。 – Jodrell 2012-08-14 14:20:34

回答

11

C#volatile不提供所有担保。它可能仍旧读取陈旧的数据。更好地使用底层操作系统同步机制,因为它提供了更强大的保证。

这一切都是由Eric利珀特(确实值得一读),大深度discussed,这里是短报价:

在C#中,“挥发性”不仅意味着“确保编译器和 抖动请勿对此变量执行任何代码重新排序或注册缓存优化。这也意味着“告诉处理器 做任何他们需要做的事情来确保我正在读取最新值,即使这意味着停止其他处理器并使它们与主存储器同步其高速缓存”“。

其实,最后一点是谎言。 volatile的真实语义读取 和写入比我在这里概述的要复杂得多;在 事实它们实际上并不能保证每个处理器都停止它在 正在做的事情并更新到/从主内存的高速缓存。相反,他们提供了关于在读取之前和之后如何存储器访问的较弱保证,并且可以观察到写入相对于彼此是有序的。 某些操作(如创建新线程,输入锁或使用Interlocked系列方法之一)会引入更强大的关于排序观察的保证。如果您需要更多详细信息,请参阅 阅读C#4.0规范的第3.10和10.5.3节。

坦率地说,我劝阻你永远做一个不稳定的领域。易变的 字段表示您正在做一些彻头彻尾的疯狂操作:您是 尝试在两个不同线程 上读取和写入相同的值,而没有锁定就位。锁确保内存读取或在锁内修改内存被认为是一致的,锁保证 只有一个线程一次访问给定的内存块,因此 。

+0

+1,因为这更详细地重申了我的第一点 – Jodrell 2012-08-14 14:23:28

+0

这里的易失性工作很好。任何写入其中的线程都将其所有内存更新推送到主内存。任何读取的线程都会从main中刷新其工作内存。因此,一个volatile的_write_(以及它所引用的数据)将被来自该volatile的任何_read_看到。也就是说,在这种情况下,AutoResetEvent看起来好多了。 – RalphChapin 2012-08-14 17:14:54

+1

@RalphChapin在多处理器系统上AFAIK只有*弱*保证所有处理器都会看到更新的值。 – oleksii 2012-08-14 17:24:45

3

恕我直言AutoResetEvent更好,因为在这种情况下,您无法忘记关键字volatile

0

这取决于如果上面的代码片段是你所做的或不是。正如其名称所示,AutoReset事件在WaitOne传递后重置。这意味着你可以直接使用它,而不是使用布尔,必须将其设置回真。

1

在使用volatile关键字之前,您应该阅读this,我想,在研究多线程时,您可以阅读整篇http://www.albahari.com/threading/文章。

它解释了volatile关键字的微妙之处以及它的行为可能会出乎意料的原因。


你会注意到,在使用时volatile,读取和写入可以得到重新排序,这可能导致在密切并发情况下的额外的迭代。在这种情况下,您可能需要等待一秒左右。


看后,我不认为你的代码工作有以下几个原因,

的“布尔”片段总是睡约一秒钟,可能不是你想要的。

“AutoResetEvent:”snippet doesen没有实例化_stop,并且总是至少运行一次doWork()

+0

我在_stop.WaitOne()上忘了'!'。这些例子有点简单,但基本上我想定期运行'doWork()'。 “DoWork()”会运行直到它完成工作,之后它必须等待一段时间才能积累新的工作。 – Sjoerd 2012-08-14 14:33:38

+0

@Sjoerd,我编辑了我的最后一部分。围绕“volatile”的警告仍然是有关的。 – Jodrell 2012-08-14 14:42:22

6

挥发性不够好,但实际上它会一直工作,因为操作系统调度程序总是会最终锁定。并且可以在一个强大的内存模型的核心上运行良好,比如x86,它会在内核之间保持高速缓存的同步。

所以真正重要的是线程将多快响应停止请求。测量起来很简单,只需在控制线程中启动一个秒表,并记录工作线程中while循环之后的时间。结果我从重复服用1000个样本和取平均测量,重复10次:

volatile bool, x86:   550 nanoseconds 
volatile bool, x64:   550 nanoseconds 
ManualResetEvent, x86:  2270 nanoseconds 
ManualResetEvent, x64:  2250 nanoseconds 
AutoResetEvent, x86:  2500 nanoseconds 
AutoResetEvent, x64:  2100 nanoseconds 
ManualResetEventSlim, x86: 650 nanoseconds 
ManualResetEventSlim, x64: 630 nanoseconds 

要注意的是对于挥发性布尔结果是非常不可能的外观,以及在处理器上带有弱存储器模型,如ARM或Itanium。我没有一个测试。

显然它看起来像你想支持ManualResetEventSlim,给予良好的性能和保证。

一个注意到这些结果,他们测量工作线程运行一个热循环,不断测试停止条件,并没有做任何其他工作。这与真实代码不太匹配,线程通常不会检查经常停止的情况。这使得这些技术之间的差异在很大程度上是无关紧要的。