很多文章说:不要使用锁来同步长操作,我遵循这条规则。但我很好奇它到底有什么不妥?这是关于一些宝贵的资源消耗吗?
另外,如果我使用AutoResetEvent会有帮助吗?或者我宁愿忘记锁定长操作并进行异步编程?
答案 0 :(得分:16)
首先,如果你不理解规则的原因,那么你就是货架式编程。您了解这些规则的原因是非常重要的,而不是盲目地应用它们。
因此,在短时间内持有锁定的两个原因是:
(1)长锁等性能差。假设您拥有一次只能有一个人可以使用的资源。你真的希望你的室友在淋浴时看着netflix吗?不,你希望他们进入并离开,以便其他人可以使用它。
(2)长锁等于死锁。如果你长时间处于锁定状态,那么你可能会调用大量代码。这会增加您调用的代码具有您不知道的锁定顺序反转的几率。
因此,标准建议是:锁定尽可能少的时间,并且有许多细粒度的锁而不是单个粒度的锁。
当然,标准建议也是因为拥有许多细粒度锁 会增加死锁的机会,因为现在你有更多可以反转的锁。并且有许多细粒度的锁也增加了一个未知的种族的可能性,在这两个锁定的区域之间有一些你没有考虑过的东西。因此标准建议是:使用少量粗粒度锁来锁定大部分代码。
标准建议是矛盾的。这是为什么?因为通过监视器锁定管理多线程程序中的共享内存从根本上是一个坏主意。这是标准做法,但这并不能使它变好。
另外,如果我使用AutoResetEvent会有帮助吗?或者我宁愿忘记锁定长操作并进行异步编程?
我的建议是尽可能使用最高级别的操作。如果你可以在没有异步的情况下逃脱,那就去做吧。如果不是,请考虑进程级并行或单线程异步,具体取决于您是处理器绑定还是IO延迟限制。如果那些因任何原因无法工作,那么将线程视为具有 no 共享内存的轻量级进程;使用TPL为您管理线程。
等等。处理锁定或互锁操作或挥发性应该是最后的手段,这些工具应该用作构建更高级别工具的原语。
答案 1 :(得分:1)
背后的简单想法。 Lock
基本上阻止其他线程进入关键部分。说关键部分已经有一个thread
正在做一些工作,然后另一个线程正在等待访问关键部分。这使得事件中的这些threads
同步执行。这提出了一个问题,为什么你不会只使用单线程呢?
所以第一个原因是你不妨为它运行单线程。
另一点,通过制作关键部分长操作,您还可以阻止等待锁定的所有其他线程被解除,从而可能会增加thread
数量的代码执行时间。
第二个原因,在高度并发的环境中,您的代码将非常慢。
答案 2 :(得分:0)
关于长锁的另一个重要事项 - >优先级倒置(见link)。让我们考虑以下几点。
低prio线程长时间锁定资源。高prio线程还需要访问此资源。最坏的情况:低prio线程持有锁,启动高prio线程并访问锁。在这种情况下,低prio线程被安排为“非活动”而高prio线程被安排为“活动”。问题:高prio线程被阻止,因为低prio线程仍然保持锁定。
我知道当你阅读它时这个问题很明显,但在复杂的代码中,事情并不总是那么清楚......减少锁定范围有助于避免这种情况。
如果可能,请切换到异步,但也要注意也可能存在死锁情况(例如,请参阅此post)。
答案 3 :(得分:0)
C#lock语句用于限制来自多个线程的关键部分的访问。此语句确保单个线程执行的互斥访问,直到锁定释放。例如,如果在写入操作正在进行时将文件写入条件设置为具有阻止所有访问权限,则在文件写入代码周围包装锁定语句确保满足条件。
现在,如果执行时间延长,其他线程在锁定时排队。这导致响应时间缓慢,性能下降或某些时候由于超时导致呼叫失败。示例 -
private Object lockObj = new Object();
public void FileOperation(decimal amount)
{
//Subsequent thread calls will queue up
lock (lockObj)
{
using (System.IO.StreamWriter file =
new System.IO.StreamWriter(@"C:\Users\Public\TestFolder\WriteLines2.txt", true))
{
//Long running operation
for(var i = 1; i < 1000000; i++)
{
file.WriteLine("Fourth line");
}
}
}
}