什么时候应该使用锁?仅在修改数据或访问数据时?
public class Test {
static Dictionary<string, object> someList = new Dictionary<string, object>();
static object syncLock = new object();
public static object GetValue(string name) {
if (someList.ContainsKey(name)) {
return someList[name];
} else {
lock(syncLock) {
object someValue = GetValueFromSomeWhere(name);
someList.Add(name, someValue);
}
}
}
}
整个区块周围是否存在锁定或者是否可以将其添加到实际修改中?我的理解是,仍然可能存在一些竞争条件,其中一个呼叫可能没有找到并开始添加它,而另一个呼叫可能也会遇到相同的情况 - 但我不确定。锁定仍然令人困惑。我没有遇到上述类似代码的任何问题,但到目前为止我可能很幸运。上面的任何帮助都会被评估,以及任何有关如何/何时锁定对象的好资源。
答案 0 :(得分:8)
您也必须在读取时锁定,或者您可以获得不可靠的数据,或者甚至是并发修改在物理上更改目标数据结构时的异常。
在上面的例子中,您需要确保多个线程不会同时尝试添加值,因此在检查它是否已存在时至少需要一个读锁定。否则多个线程可能决定添加,找不到该值(因为此检查未锁定),然后所有尝试依次添加(获取锁定后)
如果您有多次读取且只有少量写入,则可以使用ReaderWriterLockSlim。在上面的代码中,一旦您决定需要添加它,您将获得读锁以进行检查并升级到写锁。在大多数情况下,只需要一个读锁(允许你的读者线程仍然并行运行)。
可用.Net 4锁定原语here的摘要。在你深入研究多线程代码之前,你肯定应该理解这一点。选择正确的锁定机制可以产生巨大的性能差异。
你是幸运的,到目前为止你很幸运 - 这是并发错误的常见特征。如果没有针对性的负载测试,它们通常难以重现,这意味着正确的设计(当然,详尽的测试)对于避免令人尴尬和混乱的生产错误至关重要。
答案 1 :(得分:1)
在检查是否存在name
之前锁定整个块。否则,理论上,另一个线程可以在检查和添加它的代码之间添加它。
实际上在执行添加时锁定实际上根本没有做任何事情。所做的就是阻止另一个线程同时添加内容。但是因为其他线程已经决定它会进行添加,所以只要锁定被释放就会尝试执行它。
答案 2 :(得分:1)
如果多个线程只能访问资源 ,则不需要任何锁定。
如果多个线程可以访问资源并且可以修改,则需要同步所有访问/修改。在您的示例中,如果GetValueFromSomeWhere
需要很长时间才能返回,则可能会在name
中使用相同的值进行第二次调用,但该值尚未存储在{{1}中}}。
答案 3 :(得分:0)
ReaderWriterLock或精简版,如果你在4.0以下。
您将获取读取器锁定读取(将允许并发读取)并在写入某些内容时将锁定升级到写入器锁定(此时将只允许一次写入并将阻止所有读取,直到完成,以及并发写线程。)
确保使用模式释放锁定以避免死锁:
void Write(object[] args)
{
this.ReaderWriterLock.AquireWriteLock(TimeOut.Infinite);
try
{
this.myData.Write(args);
}
catch(Exception ex)
{
}
finally
{
this.ReaderWriterLock.RelaseWriterLock();
}
}