想法是从
获取一个值(“数据”)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.
EDIT2 :
上面的代码可能与.NET< 4.0,所以不允许在那里使用Lasy。我现在唯一的问题是,如果我使用双重检查锁定,我应该使用volatile吗?
volatile string[] _data;
volatile bool[] _isDataLoaded.
答案 0 :(得分:3)
我可以看到两个明显的线程问题来源
_data
和_isDataLoaded
的类型是什么? 如果这些类型不是线程安全的,则代码 arrays are only thread safe if no two threads access the same element也不是,这可能发生在上面的代码中。请尝试使用ConcurrentDictionary代替。GetCachedConstant
和CacheToFile
的确切实现,存在潜在的竞争条件,即线程A可以开始将缓存数据写入文件,从而导致线程B沿File.Exists
路径向下移动实际上文件只包含部分写入的数据我猜测罪魁祸首是这两个中的第一个 - 可能还有其他问题,但除非这些类型是线程安全的,否则没有多少双重检查锁定会保存你,除非你同步访问这些对象。
答案 1 :(得分:2)
我将_data
替换为Lazy<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模型没有。内存模型问题非常微妙,容易出错。所以我强烈建议不要使用这种模式。
答案 2 :(得分:1)
为什么不锁定方法的开头。这可以确保您的数据(缓存)始终处于有效/一致状态。
答案 3 :(得分: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];
}
}
答案 4 :(得分:1)
lock (_locker)
{
_isDataLoaded[id - 1] = true;
_data[id - 1] = cachedData;
}
此处_isDataLoaded
设置在_data
之前,因此有人在_isDataLoaded
初始化之前会看到_data
并从_data
读取。
但逆转它并不能解决问题。不能保证另一个线程会以相同的顺序看到分配,因为读者不使用任何锁定或内存障碍。
检查维基百科有关使用C#执行此操作的正确方法。 http://en.wikipedia.org/wiki/Double-checked_locking
答案 5 :(得分:0)
双重检查锁定背后的想法就是它的名字。你在lock
之前检查你的状况(比如在你的代码中),但是再次在lock
块内检查你的情况,以确保另一个线程没有改变状态(即条件的结果)线程在(成功)检查和lock
语句之间。