Introduction
I’ve been reading many of Martin Fowler’s design patterns (and many other design patterns) lately and have come to appreciate them. What I realize is that a design pattern does not necessarily have to be complex to learn, understand, and apply. The pattern just has to solve a particular problem situation. At the very least, the pattern should help the developer create code that is flexible and easy to modify.
Having said that, I have not seen many widespread patterns that approach the topic of helping developers to debug their multi-threaded programs better. We can all agree that debugging multi-threaded code can easily become a nightmare. Tracking down which threads are currently waiting for a lock, which threads are currently executing, and which threads are about to release a lock can be cumbersome.
Background
I wanted to develop a simple design pattern that could make multi-threading debugging much easier for developers. In this article, I introduce the TalkativeLocker Design Pattern.
Details
The TalkativeLocker Pattern is designed to isolate the locking region of the code from the rest of your code. There is a single point where a lock is entered, acquired, and released. In a way, you are really wrapping the area. By isolating this region, you can write logging and debugging information at each point of the locking and execution stages of any thread that enters.
Here is an extremely simple class that demonstrates the TalkativeLocker Pattern:
public class TalkativeLocker<T>
{
private readonly static object toLock = new object();
public T EnterLock(IClient client, Func<T> function)
{
T result = default(T);
Console.WriteLine(client.Name + ":Waiting for lock");
Monitor.Enter(toLock);
try
{
Console.WriteLine(client.Name + ":AquiredLock");
result = function();
}
catch (Exception ex)
{
}
finally
{
Console.WriteLine(client.Name + ":About release lock");
Monitor.Exit(toLock);
}
return result;
}
}
The class TalkativeLocker
contains a private object only used for locking: toLock
. Notice that it is static
. This means that it will be shared. Only one thread at a time will be able to acquire a lock on it. TalkativeLocker
also has a Func<T>
parameter. This parameter represents the procedure to call when the lock is acquired. Developers can use any parameter they wish. I chose Func<T>
for simplicity.
The IClient
interface parameter represents the class that will be using the TalkativeLocker
class. Here is the code for the interface:
public interface IClient
{
string Name
{
get;
set;
}
}
The interface currently only has one property: Name
. The Name
property is used by the TalkativeLocker
class to display any information that developers may see fit to make their multi-threading behavior easier while debugging. If there is more than one client thread, the Name
property should be set to a unique value: one that will identify the thread to the developer. Notice the TalkativeLocker
class using the Name
property on the IClient
interface to write out logging information to the console.
Here is a simple class that implements the interface:
public class Client : IClient
{
private string name = String.Empty;
private readonly TalkativeLocker<int> talkativeLocker =
new TalkativeLocker<int>();
public Client(string name)
{
Name = name;
}
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public int AddTwoNumbers()
{
return 1 + 2;
}
public void DoSomething()
{
int result = talkativeLocker.EnterLock(this, AddTwoNumbers);
}
}
And a sample class that demonstrates the TalkativeLocker Design Pattern:
static void Main(string[] args)
{
Client[] clients = new Client[10];
clients[0] = new Client("1");
clients[1] = new Client("2");
clients[2] = new Client("3");
clients[3] = new Client("4");
clients[4] = new Client("5");
clients[5] = new Client("6");
clients[6] = new Client("7");
clients[7] = new Client("8");
clients[8] = new Client("9");
clients[9] = new Client("10");
foreach (Client client in clients)
{
Thread t = new Thread(client.DoSomething);
t.Start();
}
Thread.CurrentThread.Join();
Console.ReadLine();
}
Notice that I’ve initialized each client with its own unique ID.
Your output will be the following:
I can now see each the status of each thread as they contend for the lock, acquire it, and ultimately release it.
Please let me know if the TalkativeLocker Design Pattern has made debugging your multi-threaded code easier.