在C#中是否应该同时使用锁(Locks)和互斥体(Mutexes)?
在C#中是否应该同时使用锁(Locks)和互斥体(Mutexes)?
这是否过度了,并且只需要其中一个?我搜索到了关于C#中互斥和锁的不同帖子,链接在这里和这里。
示例:
在我们的应用程序中,我们有一个函数,会启动多个重新连接的线程,在这个线程中我们使用了一个Mutex
和一个lock
。lock
会阻止其他线程对这部分代码的访问,从而防止connect
被其他线程更新,不是吗?
bool connect = false; Mutex reconnectMutex = new Mutex(false, "Reconnect_" + key); try { lock(site) { if(site.ContainsKey(key)) { siteInfo = (SiteInfo)site[key]; if(reconnectMutex.WaitOne(100, true)) { connect = true; } } } if (connect) { // 处理线程逻辑 } } catch {} reconnectMutex.ReleaseMutex();
更多信息:
这是在一个不运行在Web Garden中的ASP.NET WebService中。
问题的出现原因是代码中存在一个严重的bug。在代码中,Mutex只有在site.ContainsKey()
返回true
时才会被获取,但是在最后却无条件地释放了Mutex(reconnectMutex.ReleaseMutex()
)。因此,如果site.ContainsKey
返回false
,那么释放Mutex会抛出ApplicationException
,因为调用线程并不拥有该Mutex。
解决方法是在释放Mutex之前,先判断Mutex是否被当前线程拥有,可以使用Mutex.OwnedByCurrentThread
属性来判断。如果Mutex没有被当前线程拥有,则不进行释放操作,避免抛出异常。
以下是修改后的代码示例:
if (site.ContainsKey()) { // Acquire the Mutex reconnectMutex.WaitOne(); try { // Perform the necessary operations // ... } finally { // Release the Mutex only if it is owned by the current thread if (reconnectMutex.OwnedByCurrentThread) { reconnectMutex.ReleaseMutex(); } } }
通过以上修改,可以避免释放未拥有的Mutex而导致的异常。这样,在使用Mutex和Locks的同时,可以确保线程安全性,并避免出现错误和异常。
在C#中,可以使用"lock"关键字或Mutex类来实现锁定和互斥。然而,它们在行为上有很大的区别。"lock"实际上只是对Monitor.Enter/Exit的一种语法糖,而Mutex是一种多进程锁。
由于它们的不同行为,有时候需要同时在同一个应用程序或方法中使用它们,因为它们被设计用于阻塞不同的事物。然而,在你的情况下,我认为你最好研究一下Semaphore和Monitor。因为你不需要在进程之间进行锁定,所以它们可能在这种情况下是更好的选择。
解决这个问题的方法是使用Semaphore和Monitor替代Mutex。下面是一个示例代码:
Semaphore semaphore = new Semaphore(1, 1);
Monitor monitor = new Monitor();
// 使用Semaphore实现锁定
semaphore.WaitOne();
try
{
// 执行需要互斥的代码
}
finally
{
semaphore.Release();
}
// 使用Monitor实现锁定
lock (monitor)
{
// 执行需要互斥的代码
}
通过使用Semaphore和Monitor,你可以在代码中实现锁定和互斥,而无需同时使用Mutex和"lock"关键字。这样可以提高代码的可读性和可维护性,同时避免可能出现的问题。
互斥体(Mutex)因为有名称,所以阻止了同一台机器上的任何进程访问它,而锁(lock)只会阻止同一进程中的其他线程。从代码示例中,我无法看出为什么需要同时使用这两种锁。持有简单锁的时间应该很短,但重量级的进程间互斥体却被锁定了一个可能更长(尽管有重叠)的时间!直接使用互斥体可能会更简单。也许需要弄清楚是否真的需要进程间锁定。
顺便说一句,在这种情况下使用catch {}
是绝对错误的。你应该使用finally { /* 释放互斥体 */ }
。它们是非常不同的。catch会捕捉比它应该捕捉的更多种类的异常,并且还会导致响应于底层异常(如内存损坏、访问冲突等)的嵌套finally处理程序执行。所以,不应该这样写:
try { // something } catch {} // cleanup
而应该这样写:
try { // something } finally { // cleanup }
如果有特定的异常可以恢复,可以捕捉它们:
try { // something } catch (DatabaseConfigurationError x) { // 告诉用户正确配置数据库 } finally { // cleanup }
互斥体的名称必须根据机器来命名。
好的,我希望在第一句中已经澄清了这一点。
你是说我应该消除catch语句,并将其替换为finally吗?
是的。如果“处理线程逻辑”的代码抛出了一些可以恢复的异常,那么你可能还需要一个catch,但是你应该只捕捉可以恢复的异常。一般来说,“catch {} Foo();”这样做的方式是错误的,而“finally { Foo(); }”才是设计用来实现这个目的的正确方式。
尽管这超出了重复锁定问题的范围,但我已经更清楚地回答了这个问题,这可能比重复锁定问题更重要!