C# - 使用互斥锁的锁定问题
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/448550/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C# - Locking issues with Mutex
提问by Daniel Schaffer
I've got a web application that controls which web applications get served traffic from our load balancer. The web application runs on each individual server.
我有一个 web 应用程序,它控制哪些 web 应用程序从我们的负载均衡器获得服务流量。Web 应用程序在每个单独的服务器上运行。
It keeps track of the "in or out" state for each application in an object in the ASP.NET application state, and the object is serialized to a file on the disk whenever the state is changed. The state is deserialized from the file when the web application starts.
它跟踪处于 ASP.NET 应用程序状态的对象中每个应用程序的“进入或退出”状态,并且只要状态发生变化,该对象就会被序列化为磁盘上的文件。当 Web 应用程序启动时,状态从文件中反序列化。
While the site itself only gets a couple requests a second tops, and the file it rarely accessed, I've found that it was extremely easy for some reason to get collisions while attempting to read from or write to the file. This mechanism needs to be extremely reliable, because we have an automated system that regularly does rolling deployments to the server.
虽然站点本身只收到几个请求,并且它很少访问文件,但我发现由于某种原因,在尝试读取或写入文件时发生冲突非常容易。这种机制需要非常可靠,因为我们有一个自动化系统,可以定期对服务器进行滚动部署。
Before anyone makes any comments questioning the prudence of any of the above, allow me to simply say that explaining the reasoning behind it would make this post much longer than it already is, so I'd like to avoid moving mountains.
在有人对上述任何一项的审慎性发表任何评论之前,请允许我简单地说,解释其背后的推理会使这篇文章比现在更长,所以我想避免移动山。
That said, the code that I use to control access to the file looks like this:
也就是说,我用来控制对文件的访问的代码如下所示:
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;
}
}
This usuallyworks, but occasionally I cannot get access to the mutex (I see the "Could not get a lock" event in the log). I cannot reproduce this locally - it only happens on my production servers (Win Server 2k3/IIS 6). If I remove the timeout, the application hangs indefinitely (race condition??), including on subsequent requests.
这通常有效,但有时我无法访问互斥锁(我在日志中看到“无法获得锁定”事件)。我无法在本地重现这个 - 它只发生在我的生产服务器(Win Server 2k3/IIS 6)上。如果我删除超时,应用程序将无限期挂起(竞争条件??),包括后续请求。
When I do get the errors, looking at the event log tells me that the mutex lock was achieved and released by the previous request beforethe error was logged.
当我确实收到错误时,查看事件日志告诉我在记录错误之前,上一个请求已实现并释放互斥锁。
The mutex is instantiated in the Application_Start event. I get the same results when it is instantiated statically in the declaration.
互斥体在 Application_Start 事件中实例化。当它在声明中静态实例化时,我得到相同的结果。
Excuses, excuses: threading/locking is not my forté, as I generally don't have to worry about it.
借口,借口:线程/锁定不是我的强项,因为我通常不必担心。
Any suggestions as to why it randomly would fail to get a signal?
关于为什么它随机无法获得信号的任何建议?
Update:
更新:
I've added proper error handling (how embarrassing!), but I am still getting the same errors - and for the record, unhandled exceptions were never the problem.
我已经添加了正确的错误处理(多么令人尴尬!),但我仍然遇到相同的错误 - 并且为了记录,未处理的异常从来都不是问题。
Only one process would ever be accessing the file - I don't use a web garden for this application's web pool, and no other applications use the file. The only exception I can think of would be when the app pool recycles, and the old WP is still open when the new one is created - but I can tell from watching the task manager that the issue occurs while there is only one worker process.
只有一个进程会访问该文件 - 我没有为此应用程序的网络池使用网络花园,也没有其他应用程序使用该文件。我能想到的唯一例外是当应用程序池回收时,并且在创建新 WP 时旧 WP 仍处于打开状态 - 但我可以通过观察任务管理器看出问题发生时只有一个工作进程。
@mmr: How is using Monitor any different from using a Mutex? Based on the MSDN documentation, it looks like it is effectively doing the same thing - if and I can't get the lock with my Mutex, it doesfail gracefully by just returning false.
@mmr:使用 Monitor 与使用 Mutex 有何不同?根据MSDN文档,它看起来是有效地做同样的事情-如果我不能让我的互斥锁,它不会被刚刚返回false优雅地失败。
Another thing to note: The issues I'm having seem to be completely random - if it fails on one request, it might work fine on the next. There doesn't seem to be a pattern, either (certainly no every other, at least).
另一件要注意的事情:我遇到的问题似乎完全是随机的 - 如果它在一个请求上失败,它可能在下一个请求中正常工作。似乎也没有模式(当然至少没有其他模式)。
Update 2:
更新 2:
This lock is not used for any other call. The only time _lock is referenced outside the InvokeOnFile method is when it is instantiated.
此锁不用于任何其他调用。在 InvokeOnFile 方法之外引用 _lock 的唯一时间是实例化时。
The Func that is invoked is either reading from the file and deserializing into an object, or serializing an object and writing it to the file. Neither operation is done on a separate thread.
被调用的 Func 要么从文件中读取并反序列化为一个对象,要么序列化一个对象并将其写入文件。这两个操作都不是在单独的线程上完成的。
ServerState.PATH is a static readonly field, which I don't expect would cause any concurrency problems.
ServerState.PATH 是一个静态只读字段,我不希望它会导致任何并发问题。
I'd also like to re-iterate my earlier point that I cannot reproduce this locally (in Cassini).
我还想重申我之前的观点,即我无法在本地(在 Cassini 中)重现这一点。
Lessons learned:
得到教训:
- Use proper error handling (duh!)
- Use the right tool for the job (and have a basic understanding of what/how that tool does). As sambo points out, using a Mutex apparently has a lot of overhead, which was causing issues in my application, whereas Monitor is designed specifically for .NET.
- 使用正确的错误处理(废话!)
- 为工作使用正确的工具(并基本了解该工具的作用/方式)。正如 sambo 指出的那样,使用 Mutex 显然有很多开销,这会导致我的应用程序出现问题,而 Monitor 是专门为 .NET 设计的。
采纳答案by Sam Saffron
You should only be using Mutexes if you need cross-process synchronization.
如果您需要跨进程同步,您应该只使用互斥锁。
Although a mutex can be used for intra-process thread synchronization, using Monitor is generally preferred, because monitors were designed specifically for the .NET Framework and therefore make better use of resources. In contrast, the Mutex class is a wrapper to a Win32 construct. While it is more powerful than a monitor, a mutex requires interop transitions that are more computationally expensive than those required by the Monitor class.
虽然互斥锁可用于进程内线程同步,但通常首选使用 Monitor,因为 Monitor 是专门为 .NET Framework 设计的,因此可以更好地利用资源。相比之下,Mutex 类是 Win32 构造的包装器。虽然它比监视器更强大,但互斥体需要的互操作转换比 Monitor 类所需的计算成本更高。
If you need to support inter-process locking you need a Global mutex.
如果您需要支持进程间锁定,则需要一个Global mutex。
The pattern being used is incredibly fragile, there is no exception handling and you are not ensuring that your Mutex is released. That is really risky code and most likely the reason you see these hangs when there is no timeout.
正在使用的模式非常脆弱,没有异常处理,并且您无法确保释放互斥锁。这确实是有风险的代码,很可能是您在没有超时时看到这些挂起的原因。
Also, if your file operation ever takes longer than 1.5 seconds then there is a chance concurrent Mutexes will not be able to grab it. I would recommend getting the locking right and avoiding the timeout.
此外,如果您的文件操作花费的时间超过 1.5 秒,那么并发互斥锁可能无法获取它。我建议正确锁定并避免超时。
I think its best to re-write this to use a lock. Also, it looks like you are calling out to another method, if this take forever, the lock will be held forever. That's pretty risky.
我认为最好重新编写它以使用锁。此外,看起来您正在调用另一种方法,如果这需要永远,锁定将永远保持。那是相当冒险的。
This is both shorter and much safer:
这既短又安全:
// 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
回答by Sunny Milenov
If some of the file operations fail, the lock will not be released. Most probably that is the case. Put the file operations in try/catch block, and release the lock in the finally block.
如果某些文件操作失败,则不会释放锁。很可能就是这种情况。将文件操作放在 try/catch 块中,并在 finally 块中释放锁。
Anyway, if you read the file in your Global.asax Application_Start method, this will ensure that noone else is working on it (you said that the file is read on application start, right?). To avoid collisions on application pool restaring, etc., you just can try to read the file (assuming that the write operation takes an exclusive lock), and then wait 1 second and retry if exception is thrown.
无论如何,如果您在 Global.asax Application_Start 方法中读取文件,这将确保没有其他人在处理它(您说该文件是在应用程序启动时读取的,对吗?)。为避免应用程序池重启等冲突,您可以尝试读取文件(假设写操作需要排他锁),然后等待1秒,如果抛出异常重试。
Now, you have the problem of synchronizing the writes. Whatever method decides to change the file should take care to not invoke a write operation if another one is in progress with simple lock statement.
现在,您遇到了同步写入的问题。任何决定更改文件的方法都应该注意不要调用写操作,如果另一个正在使用简单的锁定语句进行。
回答by mmr
I see a couple of potential issues here.
我在这里看到了一些潜在的问题。
Edit for Update 2: If the function is a simple serialize/deserialize combination, I'd separate the two out into two different functions, one into a 'serialize' function, and one into a 'deserialize' function. They really are two different tasks. You can then have different, lock-specific tasks. Invoke is nifty, but I've gotten into lots of trouble myself going for 'nifty' over 'working'.
更新 2 的编辑:如果函数是简单的序列化/反序列化组合,我会将两者分成两个不同的函数,一个是“序列化”函数,另一个是“反序列化”函数。它们确实是两个不同的任务。然后,您可以执行不同的、特定于锁的任务。Invoke 很漂亮,但我在追求“漂亮”而不是“工作”方面遇到了很多麻烦。
1) Is your LogInformation function locking? Because you call it inside the mutex first, and then once you release the mutex. So if there's a lock to write to the log file/structure, then you can end up with your race condition there. To avoid that, put the log inside the lock.
1) 您的 LogInformation 函数是否已锁定?因为您首先在互斥锁内部调用它,然后释放互斥锁。因此,如果有一个锁定要写入日志文件/结构,那么您最终会在那里遇到竞争条件。为避免这种情况,请将日志放入锁中。
2) Check out using the Monitor class, which I know works in C# and I'd assume works in ASP.NET. For that, you can just simply try to get the lock, and fail gracefully otherwise. One way to use this is to just keep trying to get the lock. (Edit for why: see here; basically, a mutex is across processes, the Monitor is in just one process, but was designed for .NET and so is preferred. No other real explanation is given by the docs.)
2) 使用 Monitor 类检查,我知道它在 C# 中有效,我假设在 ASP.NET 中有效。为此,您可以简单地尝试获取锁,否则优雅地失败。使用它的一种方法是继续尝试获取锁。(编辑原因:参见此处;基本上,互斥锁是跨进程的,监视器仅在一个进程中,但是为 .NET 设计的,因此是首选。文档没有给出其他真正的解释。)
3) What happens if the filestream opening fails, because someone else has the lock? That would throw an exception, and that could cause this code to behave badly (ie, the lock is still held by the thread that has the exception, and another thread can get at it).
3) 如果文件流打开失败,因为其他人拥有锁会发生什么?这将引发异常,并可能导致此代码表现不佳(即,锁定仍由发生异常的线程持有,而另一个线程可以获取它)。
4) What about the func itself? Does that start another thread, or is it entirely within the one thread? What about accessing ServerState.PATH?
4) func 本身呢?这是启动另一个线程,还是完全在一个线程内?访问 ServerState.PATH 怎么样?
5) What other functions can access ServerState._lock? I prefer to have each function that requires a lock get its own lock, to avoid race/deadlock conditions. If you have many many threads, and each of them try to lock on the same object but for totally different tasks, then you could end up with deadlocks and races without any really easily understandable reason. I've changed to code to reflect that idea, rather than using some global lock. (I realize other people suggest a global lock; I really don't like that idea, because of the possibility of other things grabbing it for some task that is not this task).
5)还有哪些函数可以访问ServerState._lock?我更喜欢让每个需要锁的函数都有自己的锁,以避免竞争/死锁情况。如果您有许多线程,并且每个线程都尝试锁定同一个对象但用于完全不同的任务,那么最终可能会出现死锁和竞争,而没有任何真正容易理解的原因。我已经更改了代码来反映这个想法,而不是使用一些全局锁。(我意识到其他人建议使用全局锁;我真的不喜欢这个想法,因为其他东西可能会为一些不是这个任务的任务抢占它)。
Object MyLock = new Object();
private static Boolean InvokeOnFile(Func<FileStream, Object> 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;
}