Shared Object Pool without Thread.Sleep?
I have developed an "object pool" and cannot seem to be able to do this without using Thread.Sleep (), which I believe is "bad practice".
This relates to my other question " Is there a standard way to implement proprietary connection pooling in .net? " The idea of ββan object pool is similar to the idea of ββa connection pool used for database connections. However, in my case, I am using it to share a limited resource in a standard ASP.NET web service (works in IIS6). This means that many threads will request access to this limited resource. The pool will give out these objects ("Get"), and when all the available objects in the pool have been used, the next thread requesting one simply waits for a given amount of time for one of these objects to become available again (the thread will do a "Put" when- that's done with the object). If the object is not available at this specified time,a timeout error will occur.
Here is the code:
public class SimpleObjectPool
{
private const int cMaxGetTimeToWaitInMs = 60000;
private const int cMaxGetSleepWaitInMs = 10;
private object fSyncRoot = new object();
private Queue<object> fQueue = new Queue<object>();
private SimpleObjectPool()
{
}
private static readonly SimpleObjectPool instance = new SimpleObjectPool();
public static SimpleObjectPool Instance
{
get
{
return instance;
}
}
public object Get()
{
object aObject = null;
for (int i = 0; i < (cMaxGetTimeToWaitInMs / cMaxGetSleepWaitInMs); i++)
{
lock (fSyncRoot)
{
if (fQueue.Count > 0)
{
aObject = fQueue.Dequeue();
break;
}
}
System.Threading.Thread.Sleep(cMaxGetSleepWaitInMs);
}
if (aObject == null)
throw new Exception("Timout on waiting for object from pool");
return aObject;
}
public void Put(object aObject)
{
lock (fSyncRoot)
{
fQueue.Enqueue(aObject);
}
}
}
To use it, do the following:
public void ExampleUse()
{
PoolObject lTestObject = (PoolObject)SimpleObjectPool.Instance.Get();
try
{
// Do something...
}
finally
{
SimpleObjectPool.Instance.Put(lTestObject);
}
}
Now I have a question: how to write this so that I get rid of Thread.Sleep ()?
(Why I want to do this is because I suspect it is responsible for the "spurious" timeout I get in my testing. My test application has an object pool with 3 objects. It includes 12 threads and each thread gets an object from the pool 100 times. If a thread gets an object from the pool, it keeps going, if for 2000ms, if it doesn't, it goes to the next iteration. Now the logic dictates that 9 threads will be waiting for the object at any given time. x 2000ms is 18,000ms, which is the maximum time that any thread should have to wait for an object. My timeout is set to 60,000ms, so a thread should never time out. so something is wrong and I suspect it's Thread.Sleep)
source to share
Since you are already using lock
, use Monitor.Wait
andMonitor.Pulse
In Get()
:
lock (fSyncRoot)
{
while (fQueue.Count < 1)
Monitor.Wait(fSyncRoot);
aObject = fQueue.Dequeue();
}
And in Put()
:
lock (fSyncRoot)
{
fQueue.Enqueue(aObject);
if (fQueue.Count == 1)
Monitor.Pulse(fSyncRoot);
}
source to share
you must use a semaphore.
http://msdn.microsoft.com/en-us/library/system.threading.semaphore.aspx
UPDATE: Semaphores are one of the main constructs of multithreaded programming. A semaphore can be used in many different ways, but the basic idea is that you have a limited resource and many clients who want to use this resource, you can limit the number of clients that can access the resource at any given time.
below is a very rough example. I didn't add any errors or try / permanently block, but you should.
You can also check: http://en.wikipedia.org/wiki/Semaphore_(programming)
Let's say you have 10 buckets and 100 people who want to use those buckets. We can represent buckets in line.
Add all your buckets to the queue first
for(int i=0;i<10;i++)
{
B.Push(new Bucket());
}
Now create a semaphore to protect your bucket's queue. This semaphore is created with no elements fired and has a capacity of 10.
Semaphore s = new Semaphore(0, 10);
All clients must check the semaphore before accessing the queue. You can have 100 threads executing the thread method below. The first 10 will pass the semaphore. The rest will be waiting.
void MyThread()
{
while(true)
{
// thread will wait until the semaphore is triggered once
// there are other ways to call this which allow you to pass a timeout
s.WaitOne();
// after being triggered once, thread is clear to get an item from the queue
Bucket b = null;
// you still need to lock because more than one thread can pass the semaphore at the sam time.
lock(B_Lock)
{
b = B.Pop();
}
b.UseBucket();
// after you finish using the item, add it back to the queue
// DO NOT keep the queue locked while you are using the item or no other thread will be able to get anything out of it
lock(B_Lock)
{
B.Push(b);
}
// after adding the item back to the queue, trigger the semaphore and allow
// another thread to enter
s.Release();
}
}
source to share