【问题标题】:C# usage of lock() statements and caching of dataC# 使用 lock() 语句和缓存数据
【发布时间】:2012-02-16 15:44:21
【问题描述】:

我的应用程序中有许多静态列表,用于存储数据库中的数据并在查找信息时使用:

public static IList<string> Names;

我还有一些方法可以从数据库中刷新这些数据:

public static void GetNames()
{
    SQLEngine sql = new SQLEngine(ConnectionString);
    lock (Names)
    {
        Names = sql.GetDataTable("SELECT * FROM Names").ToList<string>();
    }
}

我最初没有设置 lock(),但是我偶尔注意到,请求线程无法在列表中找到信息。现在,我假设如果请求线程尝试访问 Names 列表,则在完全更新之前它无法访问。

这是 lock() 语句的正确方法和用法吗?

作为旁注,我在 MSDN 上注意到不应在公共变量上使用 lock()。有人可以详细说明我的特定情况吗?

【问题讨论】:

标签: c# multithreading locking


【解决方案1】:

lock 仅在 所有 打算同步的地点 应用锁定时才有用。因此每次您访问Names 时都需要lock。目前,这只会阻止 2 个线程同时交换 Names,坦率地说,这无论如何都不是问题,因为无论如何引用交换都是原子的。

另一个问题;大概Names 开始于null?你不能locknull。同样,您不应锁定可能更改参考的内容。如果要同步,常用的方法是:

// do not use for your scenario - see below
private static readonly object lockObj = new object();

然后lock(lockObj) 而不是您的数据。

关于不锁定外部可见的东西;是的。那是因为其他一些代码可能会随机选择lock就可以了,这可能会导致意外阻塞,很可能会导致死锁。

另一个大风险是您的某些代码获取名称,然后执行排序/添加/删除/清除/等 - 任何改变数据的事情。就个人而言,我会在这里使用只读列表。实际上,使用只读列表,您所拥有的只是一个参考交换;因为这是原子的,所以您不需要任何锁定:

public static IList<string> Names { get; private set; }
public static void UpdateNames() {
    List<string> tmp = SomeSqlQuery();
    Names = tmp.AsReadOnly();
}

最后:公共字段非常非常很少是一个好主意。因此上面的属性。这将由 JIT 内联,因此不是惩罚。

【讨论】:

  • +1 谢谢。我可能应该显示更多代码,名称实际上是默认实例化的。嗯,你的一些 cmets 实际上把我引向了另一个问题....
  • +++1(如果可以的话)不能相信我错过了。 {得到;私人套装; } 正是我需要的。如果仅在静态方法中更新数据,我是否还需要 lock()?
  • @Simon 取决于;如果您只是交换 reference (根据我展示的示例),那么实际上:不 - 因为引用交换是原子的。如果您要更改(编辑内容)每个人都可以访问的列表,那么 everyone 需要锁定。该锁可能很方便确保您没有 7 个线程同时交换它,但是:如果您这样做,您可能实际上想要使用 Monitor.TryEnter,超时时间为 0 毫秒,因为不是您希望 7 个线程连续更新它的情况 - 您只希望 1 个线程更新它,其余的不打扰。
  • 谢谢 Marc,我只是在交换一个参考(从数据库重新读取数据)。数据只会全部刷新(没有编辑/删除/插入)。看起来我会没事的。
  • @Simon - 那么重要的是(更新时):创建一个 new 列表,并将数据插入到任何你想要的地方; then 当它是“final”时,交换引用(理想情况下同时使其只读)。在内容完全稳定之前不要交换它。
【解决方案2】:

不,这是不正确的,因为任何人都可以直接使用 Names 属性。

public class SomeClass
{
    private List<string> _names;
    private object _namesLock = new object();

    public IEnumerable<string> Names
    {
        get
        {
            if (_names == null)
            {
                lock (_namesLock )
                {
                    if (_names == null)
                        _names = GetNames();
                }
            }

            return _names;
        }
    }

    public void UpdateNames()
    {
        lock (_namesLock)
            GetNames();
    }

    private void GetNames()
    {
        SQLEngine sql = new SQLEngine(ConnectionString);
        _names = sql.GetDataTable("SELECT * FROM Names").ToList<string>();
    }   
}

尽量避免使用静态方法。至少使用单例。

检查,锁定,检查比锁定,检查更快,因为写入只会发生一次。

根据使用情况分配属性称为延迟加载。

_namesLock 是必需的,因为您无法锁定 null。

【讨论】:

  • 相当可靠 - 但假设他想更新 Names 而不是 null - 只是想刷新数据?
  • 如果使用得当,静态方法没有任何问题。这种方法的一个问题是它可以转换回一个可变列表 - 我已经看到人们对此造成混乱,主要是通过数据绑定可枚举(这也是一个列表)意外地。数据绑定不关心(它只知道object),并愉快地对其进行变异。
  • 双重锁定需要像volatile _names这样的内存屏障。上面的代码可以返回null
【解决方案3】:

从您显示的代码中可以看出,第一次调用 GetNames() 时,Names 属性为空。我不知道空对象上的锁会做什么。我会添加一个变量来锁定。

 static object namesLock = new object();

然后在 GetNames() 中

 lock (namesLock)
 {
      if (Names == null)
        Names = ...;
 }

我们在 lock() 内部进行 if 测试以停止竞争条件。我假设 GetNames() 的调用者也做了同样的测试。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多