C# - 锁定Mutex的问题

Dan*_*fer 3 c# asp.net mutex locking

我有一个Web应用程序,可以控制哪些Web应用程序从我们的负载均衡器获得流量.Web应用程序在每个服务器上运行.

它跟踪ASP.NET应用程序状态中对象中每个应用程序的"进入或退出"状态,并且只要状态发生更改,就将对象序列化为磁盘上的文件.Web应用程序启动时,将从文件反序列化状态.

虽然网站本身只获得了几个请求,并且它很少访问该文件,但我发现由于某种原因,在尝试读取或写入文件时发生冲突非常容易.这种机制需要非常可靠,因为我们有一个自动化系统,可以定期对服务器进行滚动部署.

在任何人发表任何有关上述任何一个问题的评论之前,请允许我简单地说,解释它背后的推理会使这个帖子比现在更长,所以我想避免移山.

也就是说,我用来控制文件访问的代码如下所示:

internal static Mutex _lock = null;
/// <summary>Executes the specified <see cref="Func{FileStream, Object}" /> delegate on 
/// the filesystem copy of the <see cref="ServerState" />.
/// The work done on the file is wrapped in a lock statement to ensure there are no 
/// locking collisions caused by attempting to save and load the file simultaneously 
/// from separate requests.
/// </summary>
/// <param name="action">The logic to be executed on the 
/// <see cref="ServerState" /> file.</param>
/// <returns>An object containing any result data returned by <param name="func" />. 
///</returns>
private static Boolean InvokeOnFile(Func<FileStream, Object> func, out Object result)
{
    var l = new Logger();
    if (ServerState._lock.WaitOne(1500, false))
    {
        l.LogInformation( "Got lock to read/write file-based server state."
                        , (Int32)VipEvent.GotStateLock);
        var fileStream = File.Open( ServerState.PATH, FileMode.OpenOrCreate 
                                  , FileAccess.ReadWrite, FileShare.None);                
        result = func.Invoke(fileStream);                
        fileStream.Close();
        fileStream.Dispose();
        fileStream = null;
        ServerState._lock.ReleaseMutex();
        l.LogInformation( "Released state file lock."
                        , (Int32)VipEvent.ReleasedStateLock);
        return true;
    }
    else
    {
        l.LogWarning( "Could not get a lock to access the file-based server state."
                    , (Int32)VipEvent.CouldNotGetStateLock);
        result = null;
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

通常有效,但偶尔我无法访问互斥锁(我在日志中看到"无法获取锁定"事件).我无法在本地重现 - 它只发生在我的生产服务器(Win Server 2k3/IIS 6)上.如果我删除超时,应用程序将无限期挂起(竞争条件??),包括后续请求.

当我收到错误时,查看事件日志会告诉我,在记录错误之前,上一个请求已实现并释放了互斥锁.

互斥锁在Application_Start事件中实例化.在声明中静态实例化时,我得到相同的结果.

借口,借口:线程/锁定不是我的强项,因为我通常不必担心它.

有关为什么它会随机无法获得信号的任何建议?


更新:

我添加了正确的错误处理(多么令人尴尬!),但我仍然得到相同的错误 - 而且对于记录,未处理的异常从来都不是问题.

只有一个进程可以访问该文件 - 我没有为此应用程序的Web池使用Web园,也没有其他应用程序使用该文件.我能想到的唯一例外是当应用程序池回收时,旧的WP在创建新的WP时仍然打开 - 但是我可以通过观察任务管理器来判断问题是在只有一个工作进程的情况下发生的.

@mmr:如何使用Monitor与使用Mutex有什么不同?根据MSDN文档,它看起来是有效地做同样的事情-如果我不能让我的互斥锁,它不会被刚刚返回false优雅地失败.

还有一点需要注意:我遇到的问题似乎是完全随机的 - 如果一个请求失败,它可能在下一个问题上正常工作.似乎没有一种模式(至少没有其他模式).


更新2:

此锁定不用于任何其他呼叫.在InvokeOnFile方法之外引用_lock的唯一时间是实例化它.

调用的Func是从文件读取并反序列化为对象,或序列化对象并将其写入文件.这两个操作都不是在单独的线程上完成的.

ServerState.PATH是一个静态只读字段,我不指望它会导致任何并发问题.

我还想重新讨论我之前的观点,即我无法在本地重现(在Cassini中).


得到教训:

  • 使用正确的错误处理(呃!)
  • 使用正确的工具完成工作(并基本了解该工具的功能/方式).正如sambo指出的那样,使用Mutex显然会产生大量开销,这会导致我的应用程序出现问题,而Monitor则专门针对.NET而设计.

Sam*_*ron 15

如果需要跨进程同步,则应该只使用互斥锁.

虽然互斥锁可以用于进程内线程同步,但通常首选使用Monitor,因为监视器是专门为.NET Framework设计的,因此可以更好地利用资源.相反,Mutex类是Win32构造的包装器.虽然它比监视器更强大,但互斥锁需要互操作转换,这些转换的计算成本比Monitor类所需的更高.

如果需要支持进程间锁定,则需要Global mutex.

使用的模式非常脆弱,没有异常处理,并且您无法确保释放Mutex.这是非常危险的代码,并且很可能是您在没有超时时看到这些代码挂起的原因.

此外,如果您的文件操作时间超过1.5秒,那么并发互斥锁将无法抓取它.我建议正确锁定并避免超时.

我认为最好重新写这个来使用锁.此外,看起来你正在呼唤另一种方法,如果这需要永远,锁将永远保持.那是非常危险的.

这既短又安全:

// if you want timeout support use 
// try{var success=Monitor.TryEnter(m_syncObj, 2000);}
// finally{Monitor.Exit(m_syncObj)}
lock(m_syncObj)
{
    l.LogInformation( "Got lock to read/write file-based server state."
                    , (Int32)VipEvent.GotStateLock);
    using (var fileStream = File.Open( ServerState.PATH, FileMode.OpenOrCreate
                                     , FileAccess.ReadWrite, FileShare.None))
    {
        // the line below is risky, what will happen if the call to invoke
        // never returns? 
        result = func.Invoke(fileStream);
    }
}

l.LogInformation("Released state file lock.", (Int32)VipEvent.ReleasedStateLock);
return true;

// note exceptions may leak out of this method. either handle them here.
// or in the calling method. 
// For example the file access may fail of func.Invoke may fail
Run Code Online (Sandbox Code Playgroud)