【发布时间】:2015-04-07 21:04:58
【问题描述】:
我正在尝试实现一些管理资源池的东西,以便调用代码可以请求一个对象,如果它可用,将从池中获得一个对象,否则它将等待。但是,我无法使同步正常工作。我在泳池课中的内容是这样的(其中autoEvent 是AutoResetEvent,最初设置为信号:
public Foo GetFooFromPool()
{
autoEvent.WaitOne();
var foo = Pool.FirstOrDefault(p => !p.InUse);
if (foo != null)
{
foo.InUse = true;
autoEvent.Set();
return foo;
}
else if (Pool.Count < Capacity)
{
System.Diagnostics.Debug.WriteLine("count {0}\t capacity {1}", Pool.Count, Capacity);
foo = new Foo() { InUse = true };
Pool.Add(foo);
autoEvent.Set();
return foo;
}
else
{
return GetFooFromPool();
}
}
public void ReleaseFoo(Foo p)
{
p.InUse = false;
autoEvent.Set();
}
这个想法是,当您致电GetFooFromPool 时,您会等到收到信号后,然后尝试找到一个未使用的现有Foo。如果你找到了,我们将其设置为InUse,然后触发一个信号,以便其他线程可以继续。如果没有找到,我们检查池是否已满。如果没有,我们创建一个新的Foo,将其添加到池中并再次发出信号。如果这两个条件都不满足,我们将再次调用GetFooFromPool 再次等待。
现在在ReleaseFoo 中,我们只需将InUse 设置回false,并指示下一个线程在GetFooFromPool(如果有)中等待以尝试获取Foo。
问题似乎在于我管理池的大小。容量为5,我最终得到6Foos。我可以在我的调试行中看到count 0 出现了几次,count 1 也可能出现了几次。很明显,我有多个线程进入块,据我所知,它们不应该能够。
我在这里做错了什么?
编辑:像这样的双重检查锁:
else if (Pool.Count < Capacity)
{
lock(locker)
{
if (Pool.Count < Capacity)
{
System.Diagnostics.Debug.WriteLine("count {0}\t capacity {1}", Pool.Count, Capacity);
foo = new Foo() { InUse = true };
Pool.Add(foo);
autoEvent.Set();
return foo;
}
}
}
似乎可以解决问题,但我不确定这是不是最优雅的方法。
【问题讨论】:
-
嗯,看起来
Semaphores在这种情况下可能会更好地切芥末。这似乎就是专门为处理这种情况而设计的数据结构。 -
ConcurrentQueue可能会做你想做的一切...... -
旁注:使用
await pool.GetFooFromPoolAsync(...)可能会更好,而不是同步等待/阻塞你;会得到常规的锁定/事件。 -
顺便说一句:如果您有一个动态增长的池,那么您还应该有一些代码来缩小它。否则,一个固定大小(足够大)的池就足够了。
标签: c# multithreading pool