Friday, July 11, 2008

Locked Up!

Today's example is NOT useful code. Instead, it's a simple example of a bad practice in multi-threaded code floating around out there in programming land. This example is C# specific, but the concepts apply to other languages as well.

Since this is a bad example, I'll start off by telling you why its bad. The application locks then blocks on another resource. It's as simple as that. Many of you understand that if Thread A locks Resource RA and tries to lock Resource RB, while Thread B locks Resource RB and then tries to lock Resource RA, you have a classic "deadlock".

The following code "deadlocks". Run it and click the button a few times and it will deadlock. So, the real question is not "why is it deadlocking?" I just told you up front, it's a classic deadlock. The real question is "what are the equivalents in this program of Resources RA and RB?"

To answer this question, you can run the program in your favorite debugger and click the button a few times until it deadlocks (lengthen the sleep inside the child thread lock if need be). "Pause" your debugger to see where the threads have stopped.

So set up your project (I use VS 2008). Create a new Windows Form project, call it LockupExample. On Form1, drop a Label control and rename it lblResult. Also, on the form, drop a Button control (leave it button1). Now view the form's code, select all and replace with the following...


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
// Let's see how we can lock ourselves up
//
namespace LockupExample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
_t = new Thread(new ParameterizedThreadStart(WorkerThread));
}

private Thread _t;
private object lockObject = new object();
private void WorkerThread(object sender)
{
Thread.Sleep(1000);
while (true)
{
string result = "This is a Test";
lock (lockObject)
{
Thread.Sleep(25);
lblResult.Invoke(new MethodInvoker(delegate { lblResult.Text = result; }));
}
Thread.Sleep(500);
}
}

private void Form1_Load(object sender, EventArgs e)
{
_t.Start();
}

private void button1_Click(object sender, EventArgs e)
{
lock (lockObject)
{
lblResult.Text = "Override the Test";
}
}
}
}


I'll discuss the answer in my next post.

No comments: