我有一个Visual Studio 2008 C#.NET 3.5项目,我想拥有一个Foo
对象的线程安全池。
public class FooPool
{
private object pool_lock_ = new object();
private Dictionary<int, Foo> foo_pool_ = new Dictionary<int, Foo>();
// ...
public void Add(Foo f)
{
lock (pool_lock_)
{
foo_pool_.Add(SomeFooDescriminator, f);
}
}
public Foo this[string key]
{
get { return foo_pool_[key]; }
set { lock (pool_lock_) { foo_pool_[key] = value; } }
}
public IEnumerable<Foo> Foos
{
get
{
lock (pool_lock_)
{
// is this thread-safe?
return foo_pool_.Select(x => x.Value);
}
}
}
}
public IEnumerable<Foo> Foos { get; }
函数是否是线程安全的?或者,我是否需要克隆结果并返回一个新列表?
答案 0 :(得分:22)
不,不是。
如果在调用者枚举时另一个线程添加到字典中,则会出错。
相反,您可以这样做:
lock (pool_lock_) {
return foo_pool.Values.ToList();
}
答案 1 :(得分:19)
IEnumerable<Foo> Foos { get; }
函数是否是线程安全的?
没有
或者,我是否需要克隆结果并返回一个新列表?
不,因为那也不对。给出错误答案的线程安全方法不是很有用。
如果您锁定并制作副本,那么您要返回的内容是过去的快照。可以将集合更改为完全不同锁定释放的时刻。如果你通过复制使这个线程安全,那么你现在正在把一个充满谎言的包交给你的来电者。
当你处理单线程代码时,一个合理的模型就是一切都保持不变,除非你采取具体的措施来改变一件事。这在多线程代码中不是一个合理的模型。在多线程代码中,您应该采取相反的观点:除非您采取特定措施(例如锁定)以确保事物不会发生变化,否则一切都在不断变化。数百纳秒之前发布一系列描述世界状况的Foos有什么好处呢?在这段时间内,整个世界可能会有所不同。
答案 2 :(得分:18)
不线程安全。您需要返回ToList()
:
return foo_pool_.Select(x => x.Value).ToList();
小心延期执行!
实际上,实际代码在锁退出后运行。
// Don't do this
lock (pool_lock_)
{
return foo_pool_.Select(x => x.Value); // This only prepares the statement, does not run it
}
答案 3 :(得分:1)
您可能需要考虑SynchronizedCollection,
SynchronizedCollection类 提供一个线程安全的集合,其中包含泛型参数指定的类型的对象作为元素。
答案 4 :(得分:0)
如果您锁定每次读取访问权限,您将以非常糟糕的性能结束。在建议使用toList时,你也会每次都分配内存。
如果您使用.NET 4,请使用新线程安全集合中的ConcurrentDictionary类。它们将提供非常快速(无锁)的机制,用于从多个线程访问数据。
http://msdn.microsoft.com/en-us/library/dd997305.aspx
如果您使用的是旧的.NET版本,我建议您使用带有count变量的循环而不是foreach,如果您只添加元素而不删除它们(如示例所示),它将起作用