: C# 2008 Programmer

Using C# Lock

Using C# Lock

The Interlocked class is useful when you are performing atomic increment or decrement operations. What happens if you have multiple statements that you need to perform atomically? Take a look at the following program:

class Program {
//---initial balance amount---
static int balance = 500;
static void Main(string[] args) {
Thread t1 = new Thread(new ThreadStart(Debit));
t1.Start();
Thread t2 = new Thread(new ThreadStart(Credit));
t2.Start();
Console.ReadLine();
}
static void Credit() {
//---credit 1500---
for (int i = 0; i < 15; i++) {
balance += 100;
Console.WriteLine("After crediting, balance is {0}", balance);
}
}
static void Debit() {
//---debit 1000---
for (int i = 0; i < 10; i++) {
balance -= 100;
Console.WriteLine("After debiting, balance is {0}", balance);
}
}
}

Here two separate threads are trying to modify the value of balance. The Credit() function increments balance by 1500 in 15 steps of 100 each, and the Debit() function decrements balance by 1000 in 10 steps of 100 each. After each crediting or debiting you also print out the value of balance. With the two threads executing in parallel, it is highly probably that different threads may execute different parts of the functions at the same time, resulting in the inconsistent value of the balance variable.

Figure 10-5 shows one possible outcome of the execution. Notice that some of the lines showing the balance amount are inconsistent the first two lines show that after crediting twice, the balance is still 500, and further down the balance jumps from 1800 to 400 and then back to 1700. In a correctly working scenario, the balance amount always reflects the amount credited or debited. For example, if the balance is 500, and 100 is credited, the balance should be 600. To ensure that crediting and debiting work correctly, you need to obtain a mutually exclusive lock on the block of code performing the crediting or debiting. A mutually exclusive lock means that once a thread is executing a block of code that is locked, other threads that also want to execute that code block will have to wait.


Figure 10-5

To enable you to create a mutually exclusive lock on a block of code (the code that is locked is called a critical section), C# provides the lock keyword. Using it, you can ensure that a block of code runs to completion without any interruption by other threads.

To lock a block of code, give the lock statement an object as argument. The preceding code could be written as follows:

class Program {
//---used for locking---
static object obj = new object();
//---initial balance amount---
static int balance = 500;
static void Main(string[] args) {
Thread t1 = new Thread(new ThreadStart(Debit));
t1.Start();
Thread t2 = new Thread(new ThreadStart(Credit));
t2.Start();
Console.ReadLine();
}
static void Credit() {
//---credit 1500---
for (int i = 0; i < 15; i++) {
lock (obj) {
balance += 100;
Console.WriteLine("After crediting, balance is {0}", balance);
}
}
}
static void Debit() {
//---debit 1000---
for (int i = 0; i < 10; i++) {
lock (obj) {
balance -= 100;
Console.WriteLine("After debiting, balance is {0}", balance);
}
}
}
}

Notice that you first create an instance of an object that will be used for locking purposes:

//---used for locking---
static object obj = new object();

In general, it is best to avoid using a public object for locking purposes. This prevents situations in which threads are all waiting for a public object, which may itself be locked by some other code.

To delineate a block of code to lock, enclose the statements with the lock statement:

lock (obj) {
//---place code here---
}

As long as one thread is executing the statements within the block, all other threads will have to wait for the statements to be completed before they can execute the statements.

Figure 10-6 shows one possible outcome of the execution.


Figure 10-6

Notice that the value of balance is now consistent after each credit/debit operation.


: 0.557. /Cache: 2 / 0