Thread Safety through Self-Loading Collections
page 2 of 7
by David Penton
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 31361/ 56

Basic collection usage

When a developer wants to store similar items in a collection, there are several approaches to accomplishing that. Let us look at a basic "User" class, a collection and the "simple" way to load this.

Listing 1

public class User
{
  private int _UserId;
  public int UserId
  {
    get { return _UserId; }
    set { _UserId = value; }
  }
 
  private string _Username;
  public string Username
  {
    get { return _Username; }
    set { _Username = value; }
  }
}
 
public class MyClass
{
  static MyClass()
  {}
 
  private static Dictionary<string, User> _Users =
    new Dictionary<string, User>();
 
  public static Dictionary<string, User> Users
  {
    get { return _Users; }
  }
}

So, how is this loaded with users? A basic pattern that many developers are familiar with is:

Listing 2

string username = ... // set the username here
User user = MyClass.Users[ username ];
if ( user == null )
{
  //load user
  user = GetUser( username );
  MyClass.Users.Add( username, user );
}

That does indeed retrieve a user and then adds it to the collection. But what happens when you have many requests?

Apply the Singleton Pattern

How do we apply the Singleton Pattern to this? For a great review of the Singleton Pattern, check out this site after finishing reading this article. So, we will use a double checking lock pattern.  The reason for this is because we are dealing with a collection as our instance variable rather than a single variable. But it still leads us to several other problems. How do we make this thread safe for all items added to our collection? Or for all access to this collection?  One suggestion would be to use a single "locker" object for this.

Listing 3

public class MyClass
{
  public static readonly object Locker = new object();
 
  private static Dictionary<string, User> _Users =
    new Dictionary<string, User>();
 
  public static Dictionary<string, User> Users
  {
    get { return _Users; }
  }
}
 
// later in the code...
 
string username = ... // set the username here
User user = MyClass.Users[ username ];
if ( user == null )
{
  lock ( MyClass.Locker )
  {
    // check again for the user object
    user = MyClass.Users[ username ];
    if ( user == null )
    {
      //load user
      user = GetUser( username );
      MyClass.Users.Add( username, user );
    }
  }
}

There is a serious problem with this. For starters, the collection is completely exposed. It can be accessed directly without locking. Our pattern for loading the collection has several flaws. Let us just assume for right now that this is the only place in the code where we are loading this collection. For every new username that is requested, we are blocking the entire collection.  Under even small load, this can cause quite a bit of deadlocking. So, how are we supposed to accomplish [1] protecting the collection from extraneous access and [2] providing a framework to allow multiple items to be placed in the collection without collisions?

SingleKeyLockBox<TPrimaryKey, TValue>

We have some problems to solve here. How do we protect our collection while adjusting it? How do we handle collision management? Let us examine a code snip that should help illustrate these areas.

Listing 4

string pkey; // this is the key - or in this case the username
User value; // the variable for the User object we are trying to retrieve
 
// this is the lock object for the _PrimaryLockbox collection
// it is the central collection to the SingleKeyLockBox
lock ( _Locker )
{
  // check for the key
  if ( _PrimaryLockbox.TryGetValue( pkey, out value ) )
    return value;
}
 
// GetKeyLock manages an internal collection of locking objects
// one for each key - it isn't safe to use the key as a locking object
// it may not be an object that is safe to use for that, so always assume
// it is not
lock ( GetKeyLock( pkey.GetHashCode(), _monitorLocker, _monitorLockbox ) )
{
  // use the global collection lock just for checking the
  // collection again for this key value
  lock ( _Locker )
  {
    //added if statement - this should be the more accessed path
    if ( _PrimaryLockbox.TryGetValue( pkey, out value ) )
        return value;
  }
 
  // attempt to load the item if it isn't here
  // a more thorough explanation later.
  bool isValid = TryLoadByPrimaryKey(pkey, out value);
  if( isValid )
  {
    SetValueForPrimary(pkey, value);
  }
 
  return value;
}

The idea throughout this example is that locking the central collection should be done as "tight" as possible. Only lock the central collection for [1] checking for existence of a key, [2] placing something in the central collection, [3] removing something from the central collection.  Considering the keyed item, we only want to lock when we need to populate the central collection with data. This helps to ensure reads are not blocked (what if we ever want to reload a key?).

The method SetValueForPrimary is refactored to provide for a thread safe way of setting values in the collection.


View Entire Article

User Comments

No comments posted yet.






Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-27 4:07:44 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search