C# – 锁定Mutex的问题

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

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

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

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

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

internal static Mutex _lock = null; /// Executes the specified  delegate on /// the filesystem copy of the . /// 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. ///  /// The logic to be executed on the ///  file. /// An object containing any result data returned by . /// private static Boolean InvokeOnFile(Func 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; } } 

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

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

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

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

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


更新:

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

只有一个进程可以访问该文件 – 我没有为此应用程序的Web池使用Web园,也没有其他应用程序使用该文件。 我能想到的唯一例外是当应用程序池重新开始时,旧的WP在创建新的WP时仍然打开 – 但我可以通过观察任务管理器看出问题发生时只有一个工作进程。

@mmr:如何使用Monitor与使用Mutex有什么不同? 根据MSDN文档,看起来它实际上是在做同样的事情 – 如果我无法通过我的Mutex获得锁定,它只会返回false而优雅地失败。

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


更新2:

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

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

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

我还想重复我之前的观点,即我无法在本地重现这一点(在卡西尼号中)。


得到教训:

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

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

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

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

使用的模式非常脆弱,没有exception处理,您无法确保释放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 

如果某些文件操作失败,则不会释放锁定。 最可能就是这种情况。 将文件操作放在try / catch块中,并释放finally块中的锁。

无论如何,如果您在Global.asax Application_Start方法中读取该文件,这将确保没有其他人在使用它(您说在应用程序启动时读取该文件,对吧?)。 为了避免应用程序池恢复等的冲突,您只需尝试读取文件(假设写入操作采用独占锁定),然后等待1秒钟,如果抛出exception则重试。

现在,您遇到了同步写入的问题。 无论什么方法决定更改文件,如果另一个正在使用简单的锁定语句,则应该注意不要调用写操作。

我在这里看到了几个潜在的问题。

编辑更新2:如果函数是一个简单的序列化/反序列化组合,我将两者分成两个不同的函数,一个是’serialize’函数,另一个是’反序列化’函数。 他们真的是两个不同的任务。 然后,您可以拥有不同的特定于锁的任务。 Invoke很漂亮,但是我自己在’工作’上’漂亮’时遇到了很多麻烦。

1)您的LogInformation函数是否锁定? 因为您首先在互斥锁内部调用它,然后在释放互斥锁之后调用它。 因此,如果有一个锁写入日志文件/结构,那么你可以在那里结束你的竞争条件。 为避免这种情况,请将日志放入锁中。

2)使用Monitor类检查,我知道它在C#中工作,我假设在ASP.NET中工作。 为此,您只需尝试获取锁定,否则优先失败。 使用它的一种方法是继续尝试获取锁定。 (编辑为什么:看到这里 ;基本上,互斥是跨进程,Monitor只在一个进程中,但是是为.NET设计的,因此是首选。文档没有给出其他真正的解释。)

3)如果文件流打开失败会发生什么,因为其他人有锁? 这会引发exception,这可能会导致此代码表现不佳(即,锁仍然由具有exception的线程持有,而另一个线程可以获取它)。

4)func本身怎么样? 这会启动另一个线程,还是完全在一个线程内? 那么访问ServerState.PATH呢?

5)还有哪些其他function可以访问ServerState._lock? 我更喜欢每个需要锁的函数都有自己的锁,以避免竞争/死锁条件。 如果你有很multithreading,并且每个线程试图锁定同一个对象但是完全不同的任务,那么你最终可能会遇到死锁和竞争而没有任何容易理解的原因。 我已经改为代码来反映这个想法,而不是使用一些全局锁。 (我意识到其他人建议全局锁定;我真的不喜欢这个想法,因为其他东西可能会抓住一些不是这个任务的任务)。

  Object MyLock = new Object(); private static Boolean InvokeOnFile(Func func, out Object result) { var l = null; var filestream = null; Boolean success = false; if (Monitor.TryEnter(MyLock, 1500)) try { l = new Logger(); l.LogInformation("Got lock to read/write file-based server state.", (Int32)VipEvent.GotStateLock); using (fileStream = File.Open(ServerState.PATH, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)){ result = func.Invoke(fileStream); } //'using' means avoiding the dispose/close requirements success = true; } catch {//your filestream access failed l.LogInformation("File access failed.", (Int32)VipEvent.ReleasedStateLock); } finally { l.LogInformation("About to released state file lock.", (Int32)VipEvent.ReleasedStateLock); Monitor.Exit(MyLock);//gets you out of the lock you've got } } else { result = null; //l.LogWarning("Could not get a lock to access the file-based server state.", (Int32)VipEvent.CouldNotGetStateLock);//if the lock doesn't show in the log, then it wasn't gotten; again, if your logger is locking, then you could have some issues here } return Success; }