C# .NET4.0:更新字典及其值的线程安全方式
我有一个静态字典,我想安全地更新。最初,字典将是空的,但在应用程序的生命周期内,它将添加新的值。此外,整数值将充当可以递增和递减的单个计数器C# .NET4.0:更新字典及其值的线程安全方式,c#,multithreading,.net-4.0,C#,Multithreading,.net 4.0,我有一个静态字典,我想安全地更新。最初,字典将是空的,但在应用程序的生命周期内,它将添加新的值。此外,整数值将充当可以递增和递减的单个计数器 private static Dictionary<string, int> foo = new Dictionary<string, int>(); public static void Add(string bar) { if (!foo.ContainsKey(bar)) foo.Add(bar, 0
private static Dictionary<string, int> foo = new Dictionary<string, int>();
public static void Add(string bar)
{
if (!foo.ContainsKey(bar))
foo.Add(bar, 0);
foo[bar] = foo[bar] + 1;
}
public static void Remove(string bar)
{
if (foo.ContainsKey(bar))
{
if (foo[bar] > 0)
foo[bar] = foo[bar] - 1;
}
}
然而,我的直觉是,当我在字典中添加新条目时,这并不能解决问题,所以我会立即想到锁:
private static Dictionary<string, int> foo = new Dictionary<string, int>();
private static object myLock = new object();
public static void Add(string bar)
{
lock(myLock)
{
if (!foo.ContainsKey(bar))
foo.Add(bar, 0);
Interlocked.Increment(ref foo[bar]);
}
}
public static void Remove(string bar)
{
lock(myLock)
{
if (foo.ContainsKey(bar))
{
if (foo[bar] > 0)
Interlocked.Decrement(ref foo[bar]);
}
}
}
private static Dictionary foo=new Dictionary();
私有静态对象myLock=新对象();
公共静态无效添加(字符串栏)
{
锁(myLock)
{
如果(!foo.ContainsKey(bar))
foo.Add(bar,0);
联锁增量(参考foo[bar]);
}
}
公共静态无效删除(字符串栏)
{
锁(myLock)
{
if(食品容器(棒材))
{
如果(foo[bar]>0)
联锁减量(参考foo[bar]);
}
}
}
这种方法是理想的,还是正确的?可以改进吗?我走远了吗?锁
是好的(而且是必要的;您正确地怀疑联锁
不够),但一旦您这样做,联锁。增量
和联锁。减量
是不必要的。从多个线程访问词典
的问题是,一个线程可能会触发内部哈希表的重建,然后该线程在重建过程中被替换为另一个线程,该线程现在出现并添加到词典中,对词典的内部结构造成严重破坏
此外,您的实现很好,因为您将锁定到私有对象上,而不是这个
。正如所指出的,这个lock
对象应该是readonly
注意不要欺骗自己,让自己认为自己已经在多线程场景中实现了字典的防弹功能。例如,可能发生以下情况:
线程1在字典中查找字符串“Hello,World!”
,并接收回计数1
线程1被替换为线程2
线程2使用键调用remove“你好,世界!”
将计数设置为0
线程2被替换为线程1
线程1现在认为计数是1
,但实际上是0
最后,在.NET 4中,您应该考虑使用。请注意,这有效地消除了在调用字典上的实例方法时对锁定的需要,但并不能避免出现上述情况。如果使用.Net 4.0,可以考虑使用System.collections.Concurrent命名空间中的集合,例如,ConcurrentDictionary可能适合您。在您的情况下,字典上的添加\删除操作必须是线程安全的。这是因为如果在一个线程上枚举字典,则不希望任何其他线程修改它
要创建线程安全字典,您应该在字典上创建一个包装器,它在私有IDictionary中内部维护数据,并使用诸如Add、Remove、Clear等锁定方法
有关如何做到这一点,请参阅本SO帖子-
或者,如果您使用的是.Net 4,则可以使用提供现成线程安全性的。由于您在带有锁的Add and Remove方法中保护了对字典的多线程访问,因此不需要联锁语句,因为此代码位于这些锁的范围内。锁块中的所有代码现在都保证是单线程的,因此是安全的
这是一个很小的点,但您应该将myLock对象标记为只读,因为当前可以通过重新指定它来更改此对象。因此,您可以在一个线程锁定对象时更改该对象,随后的线程将看到不同的锁定对象,因此可以忽略以前的线程锁定。将对象标记为只读将使其不可变 Monitor.Enter()、Monitor.Exit()(lock(object))可能是您的最佳选择。但这段代码可以有所改进,尽管真正的好处可能微不足道
private static Dictionary<string, int> foo = new Dictionary<string, int>();
private static ReaderWriterLock rwLock= new ReaderWriterLock();
static int reads = 0;
static int writes = 0;
private static bool Contains(string bar)
{
try
{
rwLock.AquireReaderLock(TimeOut.Infinite);
InterLocked.Increment(ref reads);
return foo.ContainsKey(bar);
}
catch(Exception)
{
}
finally
{
rwLock.ReleaseReaderLock();
}
}
public static void Add(string bar)
{
try
{
rwLock.AquireWriterLock(TimeOut.Infinite);
if (!ContainsKey(bar))
{
foo.Add(bar, 0);
}
foo[bar] = foo[bar] + 1;
Interlocked.Increment(ref writes);
}
catch(Exception) {}
finally
{
rwLock.ReleaseWriterLock();
}
private static Dictionary foo=new Dictionary();
私有静态ReaderWriterLock rBlock=新的ReaderWriterLock();
静态int读取=0;
静态int写入=0;
私有静态bool包含(字符串栏)
{
尝试
{
rwLock.AquireReaderLock(TimeOut.Infinite);
联锁增量(参考读数);
返回食品容器(巴);
}
捕获(例外)
{
}
最后
{
rBlock.ReleaseReaderLock();
}
}
公共静态无效添加(字符串栏)
{
尝试
{
rwLock.AquireWriterLock(TimeOut.Infinite);
如果(!ContainsKey(bar))
{
foo.Add(bar,0);
}
foo[bar]=foo[bar]+1;
联锁增量(ref写入);
}
捕获(异常){}
最后
{
rBlock.ReleaseWriterLock();
}
}
您也可以对remove执行同样的操作。我确实在使用.Net 4.0。我将看看System.Collections.Concurrent提供了什么。由于我使用的是.NET 4.0,大家一致认为我应该用ConcurrentDictionary替换我的字典。这看起来很好。但是,如何避免上述情况?是否甚至可以使用Interlocked.Increment(ref foo[bar]),它不会给您一个编译错误,说明它在索引器或属性上不起作用?
private static Dictionary<string, int> foo = new Dictionary<string, int>();
private static ReaderWriterLock rwLock= new ReaderWriterLock();
static int reads = 0;
static int writes = 0;
private static bool Contains(string bar)
{
try
{
rwLock.AquireReaderLock(TimeOut.Infinite);
InterLocked.Increment(ref reads);
return foo.ContainsKey(bar);
}
catch(Exception)
{
}
finally
{
rwLock.ReleaseReaderLock();
}
}
public static void Add(string bar)
{
try
{
rwLock.AquireWriterLock(TimeOut.Infinite);
if (!ContainsKey(bar))
{
foo.Add(bar, 0);
}
foo[bar] = foo[bar] + 1;
Interlocked.Increment(ref writes);
}
catch(Exception) {}
finally
{
rwLock.ReleaseWriterLock();
}