ManualResetEvent -竞态条件是如何在这里发生的

本文关键字:在这里 条件 ManualResetEvent | 更新日期: 2023-09-27 17:50:05

我想确定我是否可以在这里使用ManualResetEvent来确保在并发环境中,myMethod()的内部动作永远不会并发调用

    static volatile bool _syncInitialized = false;  
    static ManualResetEvent _syncEvent = new ManualResetEvent(false);
    static object _syncLock = new object();
    void myMethod()
    {
        lock (_syncLock)       
        {
            if (!_syncInitialized)          // sync hasn't started, so 
            {
                _syncInitialized = true;    
                _syncEvent.Set();           // signal for the first time only 
            }
        }
        if (_syncEvent.WaitOne())           // wait for signal
        {
            _syncEvent.Close(); 
            _syncEvent = new ManualResetEvent(false); // reset to false 
            // actions that should be forced to run sequentially
        }
    }

EDIT -注意我使用ManualResetEvent而不是lock(),因为我希望能够添加超时,潜在的。

ManualResetEvent -竞态条件是如何在这里发生的

至少有一次出现竞争条件的机会。考虑:

线程#1执行_syncEvent.WaitOne()并成功,然后在执行_syncEvent.Close()之前被交换。线程#2出现并执行WaitOne(),也成功了。

另一个问题是,你调用Close()之后构造一个新的实例,显然是一种重置的方式。那么,想象一下,如果您调用Close(),线程被换出,下一个线程出现并试图执行WaitOne(),并抛出异常,因为对象已经关闭。

如果需要重置事件,请调用Reset()

您可能无法使用ManualResetEvent来完成此工作。正如其他人所说,ManualResetEvent用于信令,而不是互斥。

你说你将来想要实现一个超时。如果您只想让线程在锁上等待一段时间,然后在无法获得锁时退出,那么可以使用Monitor之一。接受超时值的TryEnter重载。例如:

private object _syncObject = new Object();
void MyMethod()
{
    if (!Monitor.TryEnter(_syncObject, TimeSpan.FromSeconds(5)))
    {
        return; // couldn't get the lock
    }
    try
    {
        // got the lock. Do stuff here
    }
    finally
    {
        Monitor.Exit(); // release the lock
    }
}

关于你是否真的想要释放finally中的锁有一些争论。如果代码抛出异常,那么您正在保护的资源现在可能(很可能?)处于不完整或损坏的状态。在这种情况下,您可能不想让其他线程对它进行操作。是否在遇到异常时释放锁是您必须做出的设计决策,与应用程序的需求保持一致。