2009-01-15 148 views
3

我有一个web应用程序,它控制哪些web应用程序从我们的负载平衡器获得服务流量。 Web应用程序在每台服务器上运行。C# - 锁定互斥锁问题

它跟踪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; 
    } 
} 

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

当我确实发现错误时,查看事件日志告诉我,互斥锁已被上一个请求发布,并在之前记录错误。

该互斥量在Application_Start事件中被实例化。当它在声明中静态实例化时,我会得到相同的结果。

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

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


更新:

我已经添加适当的错误处理(多么尴尬!),但我仍然得到同样的错误 - 并记录在案,未处理的异常从来不是问题。

只有一个进程会访问该文件 - 我没有为此应用程序的Web池使用Web园,也没有其他应用程序使用该文件。我能想到的唯一例外是应用程序池回收时,旧WP在创建新WP时仍处于打开状态 - 但通过观察任务管理器可以看出,只有一个工作进程存在问题。

@mmr:如何使用Monitor与使用Mutex不同?基于MSDN文档,它看起来像是有效地做同样的事情 - 如果我无法获得我的互斥锁的锁,它确实只是返回false失败。

另一件需要注意的事情:我遇到的问题似乎完全是随机的 - 如果一个请求失败,它可能在下一个工作正常。似乎没有一种模式,至少(至少绝对不是)。


更新2:

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

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

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

我还想重申一下我以前的观点,我无法在本地重现(卡西尼)。


教训:

  • 使用正确的错误处理(!咄)
  • 使用了合适的工具(并有该工具怎么样呢/一个基本的了解) 。正如sambo指出的那样,使用Mutex显然有很多开销,这导致我的应用程序出现问题,而Monitor是专门为.NET设计的。
+0

文件是单个文件在两个服务器之间共享还是每个服务器单个文件? – 2009-01-15 22:26:19

+0

每个服务器的单个文件。 – 2009-01-15 22:27:46

回答

15

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

虽然互斥可用于 帧内处理线程同步, 使用监视器通常是优选的,因为 被监视的.NET Framework 设计 特异性,并因此更好地利用资源 。相反,互斥类 类是对Win32 构造的封装。尽管它比监视器更加强大,但互斥转换需要 互斥转换,这些互转转换比Monitor类所需的 更具有 的计算成本。

如果您需要支持进程间锁定,您需要一个Global mutex

正在使用的模式非常脆弱,没有异常处理,并且您无法确保您的Mutex已经发布。这是非常危险的代码,很可能是在没有超时的情况下看到这些挂起的原因。另外,如果您的文件操作需要超过1.5秒,那么并发Mutexes将无法获取它。我会建议获得锁定权限并避免超时。

我认为最好重新写这个来使用锁。此外,它看起来像你正在呼唤另一种方法,如果这是永恒的,锁将永远举行。这非常危险。

这既是短,更安全:

// 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 
2

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

无论如何,如果您在Global.asax Application_Start方法中读取文件,这将确保没有其他人正在处理它(您说该文件是在应用程序启动时读取的,对吗?)。为了避免应用程序池休眠时发生冲突等,您可以尝试读取该文件(假设写操作采用排它锁),然后等待1秒钟,然后在发生异常时重试。

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

0

我在这里看到一些潜在的问题。

编辑更新2:如果函数是一个简单的序列化/反序列化组合,我会将这两个函数分成两个不同的函数,一个分为'serialize'函数,另一个分为'反序列化'函数。他们真的是两个不同的任务。然后,您可以拥有不同的锁定特定任务。 Invoke很漂亮,但是我为自己的'漂亮'过度'工作'而陷入了许多麻烦。

1)您的LogInformation函数是否被锁定?因为你首先在互斥体中调用它,然后释放互斥体。所以如果有一个锁被写入日志文件/结构,那么你最终可能会遇到你的竞争条件。为了避免这种情况,请将日志放入锁中。

2)签出使用Monitor类,我知道在C#中工作,我会假设在ASP.NET中的作品。为此,您可以简单地尝试获取锁定,否则将失败优雅。一种使用这种方式的方法是继续尝试获取锁定。 (编辑原因:请参阅here;基本上,跨进程的互斥锁,Monitor仅在一个进程中,但是是为.NET设计的,因此是首选的,文档没有给出其他真实的解释)。 3)如果文件流打开失败会发生什么情况,因为其他人有锁?这将抛出一个异常,并且可能导致此代码行为不良(即,锁仍然由具有该异常的线程保持,而另一个线程可以获取该异常)。

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

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; 
}