0

这个想法是从

  • 一个变量
  • 如果不存在,则从文件中
  • 如果不存在,则从服务器
public static string LoadData(int id)
{
    if (_isDataLoaded[id - 1])
    {
      return _data[id - 1];
    }

    if (File.Exists(_fileName))
    {
        bool dataExists;
        string cachedData;
        if (GetCachedConstant(id, out cachedData)) //read from a file
        {
            lock (_locker)
            {
                _isDataLoaded[id - 1] = true;
                _data[id - 1] = cachedData;
            }

            return _data[id - 1];
        }
    }

    string remoteData = GetFromServer(id);
    lock (_locker)
    {
        CacheToFile(id, remoteData); //write to a file
        _data[id - 1] = remoteData;
        _isDataLoaded[id - 1] = true;
    }

    return _data[id - 1];
}

许多线程使用此代码。尽管它看起来是线程安全的,但实际上并非如此。因此,我对其进行了测试,这给了我这个想法并让我确信。_data写作前应仔细检查现有情况。例如,它应该像通常在模式 Singleton 中使用的双重检查。

有人请让我了解如何在这里实现它?

编辑:

string[] _data;
bool[] _isDataLoaded.

编辑2

上面的代码可能与 .NET < 4.0 一起使用,因此不允许在那里使用 Lasy。我现在唯一的问题是,我应该使用 volatile 以防我使用双重检查锁定吗?

volatile string[] _data;
volatile bool[] _isDataLoaded.
4

6 回答 6

3

我可以看到两个明显的线程问题来源

我猜罪魁祸首是这两个中的第一个 - 可能还有其他问题,但除非这些类型是线程安全的,否则再多的双重检查锁定将拯救你,除非你也同步对这些对象的访问。

于 2012-11-22T16:05:49.163 回答
2

我将替换_dataLazy<string>[],其中从文件和网络中获取发生在您传入的委托中。

因此,在静态构造函数中执行以下操作:

_data=new Lazy<string>[maxId+1];
for(int i=0;i<_data.Length;i++)
{
  _data[i]=new Lazy<string>(()=>fetchData(i), LazyThreadSafetyMode.ExecutionAndPublication);
}

然后简单地获得价值_data[i].Value


如果你真的想要双重检查锁定,它基本上是这样的:

if (!_isDataLoaded[id - 1])
{
    lock(locker)
    {
        if(!_isDataLoaded[id - 1])
        {
            ...
            _data[id - 1] = ...;
            _isDataLoaded[id - 1] = true;
        }
    }
}
return _data[id - 1];

这段代码的问题是很难弄清楚它是否真的能保证工作。这取决于您运行的平台的内存模型。AFAIK .net 2.0 内存模型保证这是可行的,但 ECMA CLR 模型和 java 模型不能。内存模型问题非常微妙且容易出错。所以我强烈建议不要使用这种模式。

于 2012-11-22T16:12:38.943 回答
1

怎么样,保持一个锁来从/向缓存加载/持久化数据

public static string LoadData(int id)
{
    if (_isDataLoaded[id - 1])
      return _data[id - 1];

    lock (_locker)
    {
        if (_isDataLoaded[id - 1])
          return _data[id - 1];

        if (File.Exists(_fileName))
        {
            bool dataExists;
            string cachedData;
            if (GetCachedConstant(id, out cachedData)) //read from a file
            {
                _data[id - 1] = cachedData;
                _isDataLoaded[id - 1] = true;
                return _data[id - 1];
            }
        }

        string remoteData = GetFromServer(id);
        CacheToFile(id, remoteData); //write to a file
        _data[id - 1] = remoteData;
        _isDataLoaded[id - 1] = true;
        return _data[id - 1];
    }
}
于 2012-11-22T16:05:13.917 回答
1

为什么不在方法的开头锁定。这可以确保您的数据(缓存)始终处于有效/一致的状态。

于 2012-11-22T16:02:17.797 回答
1
    lock (_locker)
    {
        _isDataLoaded[id - 1] = true;
        _data[id - 1] = cachedData;
    }

这里_isDataLoaded是在 之前设置的,因此初始化之前_data有人看到_isDataLoaded和阅读的比赛。_data_data

但扭转它并不能解决问题。不能保证另一个线程会以相同的顺序看到分配,因为阅读器不使用任何锁或内存屏障。

检查 Wikipedia 以了解使用 C# 执行此操作的正确方法。http://en.wikipedia.org/wiki/Double-checked_locking

于 2012-11-22T16:11:25.237 回答
0

双重检查锁定背后的想法就是它的名字。您之前检查您的条件lock(如在您的代码中),但也lock再次在块内检查,以确保另一个线程没有更改状态(即条件的结果),而当前线程在(成功)检查和lock陈述。

于 2012-11-22T15:54:11.680 回答