23

我有一个 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; }线程安全的吗?或者,我是否需要克隆结果并返回一个新列表?

4

5 回答 5

22

不,不是。

如果在您的调用者枚举时另一个线程添加到字典中,您将收到错误消息。

相反,您可以这样做:

lock (pool_lock_) {
    return foo_pool.Values.ToList();
}
于 2012-04-19T16:35:50.187 回答
19

函数是IEnumerable<Foo> Foos { get; }线程安全的吗?

不。

或者,我是否需要克隆结果并返回一个新列表?

No, because that's not right either. A threadsafe method that gives the wrong answer is not very useful.

If you lock and make a copy then the thing you are returning is a snapshot of the past. The collection could be changed to be completely different the moment the lock is released. If you make this threadsafe by making a copy then you are now handing a bag full of lies to your caller.

When you are dealing with single-threaded code, a reasonable model is that everything is staying the same unless you take specific measures to change a thing. That is not a reasonable model in multi-threaded code. In multi-threaded code, you should assume the opposite: everything is constantly changing unless you take specific measures (such as a lock) to ensure that things are not changing. What is the good of handing out a sequence of Foos that describe the state of the world in the distant past, hundreds of nanoseconds ago? The entire world could be different in that amount of time.

于 2012-04-19T19:25:48.210 回答
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
}
于 2012-04-19T16:36:01.687 回答
1

You may want to consider a SynchronizedCollection,

SynchronizedCollection Class Provides a thread-safe collection that contains objects of a type specified by the generic parameter as elements.

http://msdn.microsoft.com/en-us/library/ms668265.aspx

于 2012-04-19T20:28:55.260 回答
0

If you'll lock on every read access you'll end with very bad performance. And in suggestions to use toList you'll also allocate memory every time.

If you using .NET 4 just use ConcurrentDictionary class from new thread safe collections. They will provide very fast (lock free) mechanisms for accessing data from multiple threads.

http://msdn.microsoft.com/en-us/library/dd997305.aspx

If you are using old .NET version I would suggest you to use for cycle with count variable instead of foreach it will work if you only add elements without removing them (as in your example)

于 2012-04-19T20:57:40.143 回答