2

我已经开发了一个“对象池”,如果不使用 Thread.Sleep() 似乎无法做到这一点,我认为这是“不好的做法”。

这与我的另一个问题“是否有在.net 中实现专有连接池的标准方法? ”有关。对象池背后的想法类似于用于数据库连接的连接池背后的想法。但是,就我而言,我使用它来共享标准 ASP.NET Web 服务(在 IIS6 中运行)中的有限资源。这意味着许多线程将请求访问此有限资源。池将抛出这些对象(“获取”),一旦所有可用的池对象都被使用,下一个请求的线程将简单地等待一段时间,以使这些对象中的一个再次可用(线程会这样做一次“放置”对象)。如果对象在此设定时间内不可用,则会发生超时错误。

这是代码:

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);
        }
    }
}

要使用它,可以执行以下操作:

        public void ExampleUse()
        {
            PoolObject lTestObject = (PoolObject)SimpleObjectPool.Instance.Get();
            try
            {
                // Do something...
            }
            finally
            {
                SimpleObjectPool.Instance.Put(lTestObject);
            }
        }

现在我的问题是:我该如何写才能摆脱 Thread.Sleep()?

(我为什么要这样做是因为我怀疑它是导致我在测试中遇到的“错误”超时的原因。我的测试应用程序有一个对象池,其中包含 3 个对象。它启动 12 个线程,每个线程得到从池中获取对象 100 次。如果线程从池中获取对象,它将保持 if 2,000 毫秒,如果没有,则进入下一次迭代。现在逻辑表明 9 个线程将等待对象在任何时间点。9 x 2,000 毫秒是 18,000 毫秒,这是任何线程应该等待对象的最长时间。我的获取超时设置为 60,000 毫秒,因此任何线程都不应该超时。但是有些这样做是错了,我怀疑它是 Thread.Sleep)

4

2 回答 2

5

由于您已经在使用lock,请考虑使用Monitor.WaitandMonitor.Pulse

Get()

lock (fSyncRoot)
{
   while (fQueue.Count < 1)
     Monitor.Wait(fSyncRoot);

   aObject = fQueue.Dequeue();
}

并在Put()

lock (fSyncRoot)
{
   fQueue.Enqueue(aObject);
   if (fQueue.Count == 1)
         Monitor.Pulse(fSyncRoot);
}
于 2009-10-11T00:08:25.070 回答
2

你应该使用信号量。

http://msdn.microsoft.com/en-us/library/system.threading.semaphore.aspx

更新:信号量是多线程编程的基本结构之一。信号量可以有不同的使用方式,但基本思想是当您拥有有限的资源和许多想要使用该资源的客户端时,您可以限制在任何给定时间可以访问该资源的客户端数量。

下面是一个非常粗略的例子。我没有添加任何错误检查或 try/finally 块,但你应该这样做。

您还可以查看: http://en.wikipedia.org/wiki/Semaphore_(programming)

假设您有 10 个存储桶和 100 个想要使用这些存储桶的人。我们可以表示队列中的桶。

在开始时,将所有存储桶添加到队列中

for(int i=0;i<10;i++) 
{
    B.Push(new Bucket());
}

现在创建一个信号量来保护您的存储桶队列。这个信号量是在没有触发任何项目的情况下创建的,容量为 10。

Semaphore s = new Semaphore(0, 10);

所有客户端都应该在访问队列之前检查信号量。您可能有 100 个线程运行下面的线程方法。前 10 个将通过信号量。所有其他人都会等待。

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();
    }
}
于 2009-10-11T00:23:36.930 回答