是否返回IEnumerable <>线程安全?

Pau*_*ulH 23 c# thread-safety .net-3.5

我有一个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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

public IEnumerable<Foo> Foos { get; }功能线程安全的?或者,我是否需要克隆结果并返回新列表?

SLa*_*aks 22

不,不是.

如果另一个线程在调用者枚举时添加到字典中,则会出现错误.

相反,你可以这样做:

lock (pool_lock_) {
    return foo_pool.Values.ToList();
}
Run Code Online (Sandbox Code Playgroud)


Eri*_*ert 19

IEnumerable<Foo> Foos { get; }功能线程安全的?

没有.

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

不,因为那也不对.给出错误答案的线程安全方法不是很有用.

如果您锁定并制作副本,那么您要返回的内容就是过去的快照.锁定释放时,可以将集合更改为完全不同.如果你通过复制使这个线程安全,那么你现在正在把一个充满谎言的包交给你的来电者.

当你处理单线程代码时,一个合理的模型就是一切都保持不变,除非你采取具体的措施来改变一件事.这在多线程代码中不是一个合理的模型.在多线程代码中,您应该采取相反的观点:除非您采取特定措施(例如锁定)以确保事物不会发生变化,否则一切都在不断变化.数百纳秒之前发布一系列描述世界状况的Foos有什么好处呢?在这段时间内,整个世界可能会有所不同.

  • @PaulH:我建议您需要考虑两个问题:(1)此池的用户实际需要池提供哪些服务?(2)鉴于其多线程特性,池实际可以提供哪些服务?多线程池通常不提供"列出池中的所有内容"服务,因为(1)没有人需要它,以及(2)很难以安全,准确和高效的方式提供该服务.池通常提供"从池中取出一些东西"和"将某些东西放回池中"的服务. (12认同)

Ali*_*tad 18

不是线程安全的.你需要回报ToList():

return foo_pool_.Select(x => x.Value).ToList();
Run Code Online (Sandbox Code Playgroud)

小心推迟执行!

事实是在锁退出后实际代码运行.

// Don't do this
lock (pool_lock_)
{
    return foo_pool_.Select(x => x.Value); // This only prepares the statement, does not run it
}
Run Code Online (Sandbox Code Playgroud)