views:

121

answers:

5

I have seen many good object pool implementations. For example: http://stackoverflow.com/questions/2510975/c-object-pooling-pattern-implementation.

But it seems like the thread-safe ones always use a lock and never try to use Interlocked.* operations.

It seems easy to write one that doesn't allow returning objects to the pool (just a big array with a pointer that Interlocked.Increments). But I can't think of any way to write one that lets you return objects. Has anyone done this?

+1  A: 

I cannot see any real benefit in using Interlocked also that it has to be used in an unsafe manner. Lock, is only changing a bit flag on the object's memory space - very very fast indeed. Interlocked is a tad better since it could be done on the registers and not in the memory.

Are you experiencing a performance problem? What is the main purpose of such code? At the end of the day C# is designed to abstract memory management from you so that you focus on your business problem.

Remember, if you need to manage memory yourself and use unsafe pointers, you have to pin the memory area = extra performance cost.

Aliostad
I'm not sure what you mean by saying that the lock statement "is only changing a bit flag...very very fast indeed." Monitor locks are relatively expensive operations in C#. (This is precisely the reason the controversial double-checked locking pattern exists at all)
Kirk Woll
Where did you read it? My reference is Jeffrey Richter's CLR via C#. I can give you a snippet if you want.
Aliostad
Kirk Woll
Interlocked needs to be used carefully, but it is not inherently unsafe, though it is harder to prove correctness. It absolutely is not unsafe in the .NET sense of the word. Monitor locks are indeed relatively expensive compared to a memory read, which is why double-checked pattern exists (which borrows much of its controversy unfairly from the facts about Java's different memory model, but OTOH it's not *that* expensive so often is indeed pointless). Contended locks are much more expensive than *some* lock-free patterns, though not all. There's no need to pin memory with interlocked.
Jon Hanna
Interlocked is not unsafe, using pointers are. Pinning memory is required when you need to use unsafe code, i.e. work with the pointers. Again nothing to do with Interlocked. Please read my answer again.
Aliostad
I think there's a mis-understanding. There's no need to pin anything here. If you want to pre-allocate 10k Order objects in a pool, its easy to create an Order[10000] array on startup, create the objects, and initialize an int x to 0. Then every time a thread wants to get an object from the pool, it just calls Interlocked.Increment on x and gets an object from the array using the value returned from Interlocked.Increment as an index. It's fully threadsafe since Interlocked.Increment is. I'm just not sure how to implement returing objects to the pool.
Michael Covelli
I AM CONFUSED NOW!!! Allocate 10k using what? Are you using C# at all or not? If you are using Marshall.GlobalAlloc to allocate the memory then we are not talking C# here. But if you create the objects in C#, you can never gurantee their object pointer never changes even if they are in generation 0; GC still could be moving them.
Aliostad
I am using C#. It's fine with me if they are moved around in memory. The main issue that I want to avoid at a time-critical area of my code is a garbage collection. If I had the statement "Order O = new Order(...)" in my time-critical area, this might cause a garbage collection that could freeze all threads for .1-100 ms depending on what generation it is. Instead, I do this: "Order O = PreallocPool<Order>.GetNext()" Since we're not allocating in the time-critical part of the code, we can't trigger a GC.
Michael Covelli
And After I'm done with the order, I call PreallocPool<Order>.ReturnObj(O) and then O = null. Since I'm never using more than maybe 100 order objects at a time, I'm able to get by re-using the same 100 Order objects and never allocating a new one.
Michael Covelli
Well, are you using Interlock on a pointer stored as a member variable or everytime you update it? To be honest, I do not understand the problem you are trying to solve so I do not seem to be able help so I am afraid I am out (Dragon's Den way!).
Aliostad
Ah. I'm not incrementing a literal IntPtr pointer into memory. Just an integer that is the index of the next element in my array.
Michael Covelli
+1  A: 

Think hard about why you need object pooling anyway - there is no discussion here of the objects that are pooled. For most objects, using the managed heap will provide the necessary functionality without the headaches of a new pool manager in your own code. Only if your object encapsulates hard-to-establish or hard-to-release resources is object pooling in managed code worth considering.

If you do need to do it yourself, then there is a lightweight reader/writer lock that might be useful in optimizing the pool accesses.

http://theburningmonk.com/2010/02/threading-using-readerwriterlockslim/

Steve Townsend
Right now I'm using a regular lock in my object pooling class which is about twice as fast as the reader/writer lock slim (http://www.albahari.com/threading/part2.aspx). But we're talking only 20 vs. 40 ns. Both are fast enough for most purposes. I was mostly just curious if anyone had an idea of how to make a faster one using Interlocked.* semantics.
Michael Covelli
+1  A: 

The problem with returning reference objects is that it defeats the entire attempt to lock access to it in the first place. You can't use a basic lock() command to control access to a resource outside the scope of the object, and that means that the traditional getter/setter designs don't work.

Something that MAY work is an object that contains lockable resources, and allows lambdas or delegates to be passed in that will make use of the resource. The object will lock the resource, run the delegate, then unlock when the delegate completes. This basically puts control over running the code into the hands of the locking object, but would allow more complex operations than Interlocked has available.

Another possible method is to expose getters and setters, but implement your own access control by using a "checkout" model; when a thread is allowed to "get" a value, keep a reference to the current thread in a locked internal resource. Until that thread calls the setter, aborts, etc., all other threads attempting to access the getter are kept in a Yield loop. Once the resource is checked back in, the next thread can get it.

public class Library
{
   private Book controlledBook
   private Thread checkoutThread;

   public Book CheckOutTheBook()
   {
      while(Thread.CurrentThread != checkoutThread && checkoutThread.IsAlive)
          thread.CurrentThread.Yield();

      lock(this)
      {
         checkoutThread = Thread.CurrentThread;

         return controlledBook;
      }
   }

   public void CheckInTheBook(Book theBook)
   {
      if(Thread.CurrentThread != checkoutThread)
          throw new InvalidOperationException("This thread does not have the resource checked out.");

      lock(this)
      {
         checkoutThread = null;

         controlledBook = theBook;
      }
   }

}

Now, be aware that this still requires some cooperation among users of the object. Particularly, this logic is rather naive with regards to the setter; it is impossible to check in a book without having checked it out. This rule may not be apparent to consumers, and improper use could cause an unhandled exception. Also, all users must know to check the object back in if they will stop using it before they terminate, even though basic C# knowledge would dictate that if you get a reference type, changes you make are reflected everywhere. However, this can be used as a basic "one at a time" access control to a non-thread-safe resource.

KeithS
Never, ever lock on `this` other than in a private inner class (and even then...) as it will conflict with outer code blocking on the same instance. Also, the checkout thread technique does nothing other than cause non-idiomatic version of the same thing offered by a longer-term Monitor. You can also not require such user-cooperation by controlling the access inside of a collection before the object is visible to the callers.
Jon Hanna
@Jon Hanna: If users of a class are going to need exclusive access to an instance of that class, the most natural thing for them to lock on will be that object instance. If the class wishes to honor such locking semantics for its users, wouldn't the most natural way to do so be for it to lock on "this"? Yes, there can be problems, but if the design requires outsiders to be able to demand exclusive access what alternative is there?
supercat
@supercat The users should indeed lock on the instance in question, which is precisely why code in the class itself shouldn't lock on `this` as then the two different pieces of code could interact nastily. Assume that from outside a lock may be held at any time on the object, and from the inside lock on either an internal object that is similarly the most natural one to lock on, or if none fits lock on an object that exists just for that reason. Don't lock on something visible to other code, only on private objects.
Jon Hanna
+2  A: 

I've done it with a lock-free queue built as a singly-linked list. The following has some irrelevant stuff cut out and I haven't tested it with that stuff removed, but should at least give the idea.

internal sealed class LockFreeQueue<T>
{
  private sealed class Node
  {
    public readonly T Item;
    public Node Next;
    public Node(T item)
    {
      Item = item;
    }
  }
  private volatile Node _head;
  private volatile Node _tail;
  public LockFreeQueue()
  {
    _head = _tail = new Node(default(T));
  }
#pragma warning disable 420 // volatile semantics not lost as only by-ref calls are interlocked
  public void Enqueue(T item)
  {
    Node newNode = new Node(item);
    for(;;)
    {
      Node curTail = _tail;
      if (Interlocked.CompareExchange(ref curTail.Next, newNode, null) == null)   //append to the tail if it is indeed the tail.
      {
        Interlocked.CompareExchange(ref _tail, newNode, curTail);   //CAS in case we were assisted by an obstructed thread.
        return;
      }
      else
      {
        Interlocked.CompareExchange(ref _tail, curTail.Next, curTail);  //assist obstructing thread.
      }
    }
  }    
  public bool TryDequeue(out T item)
  {
    for(;;)
    {
      Node curHead = _head;
      Node curTail = _tail;
      Node curHeadNext = curHead.Next;
      if (curHead == curTail)
      {
        if (curHeadNext == null)
        {
          item = default(T);
          return false;
        }
        else
          Interlocked.CompareExchange(ref _tail, curHeadNext, curTail);   // assist obstructing thread
      }
      else
      {
        item = curHeadNext.Item;
        if (Interlocked.CompareExchange(ref _head, curHeadNext, curHead) == curHead)
        {
          return true;
        }
      }
    }
  }
#pragma warning restore 420
}

If your reason for pooling was the raw performance consideration of allocation and collection then the fact that this allocates and collects makes it pretty useless. If it's because an underlying resource is expensive to obtain and/or release, or because the instances cache "learned" information in use, then it may suit.

Jon Hanna
Thanks for the example! My reason for pooling is the possibility of a collection during allocation. So that doesn't quite work because of the allocations. But something along those lines...
Michael Covelli
What's the problem with a collection during allocation? It won't collect what's being allocated. If you can have the class itself designed to work that way (rather than needing to add to a support without changing the class) then you can easily build a freelist along the same way. If this was more than an experiment, then you are likely solving the wrong problem though. If you need the sort of precise control of execution that subverting the GC entails, then you are likely better of not sharing between threads either. Remember that while multithreading can make for more efficient sharing ...
Jon Hanna
... of tasks, a single thread will always be more efficient in itself, if not more responsive. I can only think of a couple of cases where subverting the GC to that extent is used, and they all keep all threads completely separate.
Jon Hanna
This said, if you are going to subvert the GC this much you must do it for the **entire** system. Otherwise, something else could trigger a collection. This in itself will rule out some of the best lock-free algorithms. The above for example, cannot be ported straight to a non-GC framework, as the more direct translations would suffer the ABA problem (indeed, it's close to an example I've seen demonstrating the ABA problem!). It's GC that makes the remaining ABA issue with the above no longer an issue (what would be a lost pointer in C++ is just GC fodder here).
Jon Hanna
+1  A: 

Have you looked at the Concurrent collection in .Net 4.

e.g. http://msdn.microsoft.com/en-us/library/dd287191.aspx

DotDot