我试图实现管理资源池的东西,以便调用代码可以请求一个对象,并且如果它可用,将从池中获得一个对象,否则它将被用于等待。但是,我无法让同步正常工作。我在我的池类中的内容是这样的(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
。< / p>
问题似乎在于我管理池的大小。容量为5
时,我最终会使用6
Foo
s。我可以在调试行中看到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;
}
}
}
似乎解决了这个问题,但我不确定它是最优雅的方法。
答案 0 :(得分:2)
您正在做的事情存在一些问题,但您的具体竞争状况可能是由以下情况引起的。想象一下,你有一个容量。
1)池中有一个未使用的项目。
2)线程#1抓住它并发出信号。
3)线程#2找不到可用事件并进入容量块。 尚未添加该项目。
4)线程#1将项目返回到池中并发出事件信号。
5)使用另外两个线程(例如#3,#4)重复步骤1,2和3。
6)线程#2 将一个项目添加到池中。
7)线程#4 将一个项目添加到池中。
池中现在有两个容量为1的项目。
但是,您的实施还存在其他潜在问题。
答案 1 :(得分:2)
正如评论中已经提到的,计数信号量是你的朋友。 将它与并发堆栈相结合,您就可以获得一个简单的线程安全实现,您仍然可以懒惰地分配池项目。
下面的简单实现提供了此方法的示例。请注意,此处的另一个优点是您不需要使用InUse
成员“污染”您的池项目作为跟踪内容的标记。
请注意,作为微优化,在这种情况下,堆栈优先于队列,因为它将提供池中最近返回的实例,该实例可能仍在例如L1缓存。
public class GenericConcurrentPool<T> : IDisposable where T : class
{
private readonly SemaphoreSlim _sem;
private readonly ConcurrentStack<T> _itemsStack;
private readonly Action<T> _onDisposeItem;
private readonly Func<T> _factory;
public GenericConcurrentPool(int capacity, Func<T> factory, Action<T> onDisposeItem = null)
{
_itemsStack = new ConcurrentStack<T>(new T[capacity]);
_factory = factory;
_onDisposeItem = onDisposeItem;
_sem = new SemaphoreSlim(capacity);
}
public async Task<T> CheckOutAsync()
{
await _sem.WaitAsync();
return Pop();
}
public T CheckOut()
{
_sem.Wait();
return Pop();
}
public void CheckIn(T item)
{
Push(item);
_sem.Release();
}
public void Dispose()
{
_sem.Dispose();
if (_onDisposeItem != null)
{
T item;
while (_itemsStack.TryPop(out item))
{
if (item != null)
_onDisposeItem(item);
}
}
}
private T Pop()
{
T item;
var result = _itemsStack.TryPop(out item);
Debug.Assert(result);
return item ?? _factory();
}
private void Push(T item)
{
Debug.Assert(item != null);
_itemsStack.Push(item);
}
}