13

我在工作线程中有一个对象,我可以指示它停止运行。我可以使用 bool 或 AutoResetEvent 来实现它:

布尔值:

private volatile bool _isRunning;

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

自动复位事件:

private AutoResetEvent _stop;

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

然后该Stop()方法将设置_isRunning为 false,或调用_stop.Set().

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

4

5 回答 5

12

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

所有这些都由 Eric Lippert 深入讨论(真的值得一读),这里是简短的引用:

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

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

坦率地说,我不鼓励你创建一个 volatile field。易失性字段表明您正在做一些彻头彻尾的疯狂事情:您试图在两个不同的线程上读取和写入相同的值而没有设置锁定。锁保证观察到锁内的内存读取或修改是一致的,锁保证一次只有一个线程访问给定的内存块,等等。

于 2012-08-14T13:45:21.437 回答
7

Volatile 还不够好,但实际上它总是可以工作,因为操作系统调度程序最终总是会锁定。并且将在具有强大内存模型的内核上运行良好,例如 x86,它消耗大量资源来保持内核之间的缓存同步。

所以真正重要的是线程响应停止请求的速度有多快。很容易测量,只需在控制线程中启动一个Stopwatch,并在工作线程中记录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 等内存模型较弱的处理器上,volatile bool 的结果不太可能看起来那么好。我没有一个要测试。

显然,您似乎想要支持 ManualResetEventSlim,提供良好的性能和保证。

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

于 2012-08-15T01:17:03.547 回答
3

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

于 2012-08-14T13:25:38.160 回答
1

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

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


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


看了之后,我不认为你的代码有几个原因,

"boolean:" 片段总是休眠大约一秒钟,这可能不是您想要的。

“AutoResetEvent:”片段不会实例化_stop,并且总是doWork()至少运行一次。

于 2012-08-14T13:36:26.047 回答
0

这取决于上面的代码片段是否就是你所做的一切。AutoReset 事件,顾名思义,在 WaitOne 传递后被重置。这意味着您可以立即再次使用它,而不是使用 bool,而必须将其设置回 true。

于 2012-08-14T13:29:16.023 回答