Any application must have one or more processes. A Process is structural unit with a memory block and using some set of resources. For each executable, the Windows OS creates some isolated memory block. This article tries to explain the use of lock object to make your Multithreaded piece of code thread safe in C# .Net.
Every process must have at least one thread. The first thread is created with a process and is known as primary thread. This Primary Thread is entry point of application. In traditional Windows applications it is the method WinMain() and in console applications it is named main().
Main goal of creating multithreading application is performance improvement. As an example, imagine a situation where in a user starts a long process (e.g. copying), he can?t use a single threaded application and wait for an infinite time for the operation to get completed. But if he uses multithreading application he can set copying process in the background and interact with application without any problems.
At first, if one wants to create a multi-threaded application an important point to be remembered is, a global variable or static variable, which is being accessed by different threads, can try to modify the same variable or get accessed by multiple threads at the same point of time. This is a generic problem, which is solved using a mechanism called Synchronization of threads. Synchronization is nothing but the process of creating some set of rules to operate data or resources.
The C# .Net language has a powerful namespace which can be used for programming with Threads as well as Thread Synchronization in C# .Net programming. The name of the namespace is Sytem.Threading. The most important class inside this namespace for manipulating the threads is the C# .Net class Thread. It can run other thread in our application process.
lock Statement
There is a new keyword lock in .Net C# . This provides a mechanism for synchronizing the thread operation. It means at the same point of time only one thread can access to this method of created object. Unless the lock is released after completion of the code, the next routine or iteration cannot enter the block.
The lock keyword marks a statement block as a critical section by obtaining the mutual-exclusion lock for a given object, executing a statement, and then releasing the lock. This statement takes the following form:
Object thisLock = new Object();
lock (thisLock)
{
// Critical code section
}
lock ensures that one thread does not enter a critical section of code while another thread is in the critical section. If another thread attempts to enter a locked code, it will wait, block, until the object is released.
The following sample shows a simple use of threads in C#.:
// statements_lock.cs
using System;
using System.Threading;
class ThreadTest
{
public void RunMe()
{
Console.WriteLine("RunMe called");
}
static void Main()
{
ThreadTest b = new ThreadTest();
Thread t = new Thread(b.RunMe);
t.Start();
}
}
The following sample uses threads and lock. As long as the lock statement is present, the statement block is a critical section and balance will never become a negative number
// statements_lock2.cs
using System;
using System.Threading;
class Account
{
private Object thisLock = new Object();
int balance;
Random r = new Random();
public Account(int initial)
{
balance = initial;
}
int Withdraw(int amount)
{
// This condition will never be true unless the lock statement
// is commented out:
if (balance < 0)
{
throw new Exception("Negative Balance");
}
// Comment out the next line to see the effect of leaving out
// the lock keyword:
lock(thisLock)
{
if (balance >= amount)
{
Console.WriteLine("Balance before Withdrawal : " + balance);
Console.WriteLine("Amount to Withdraw : -" + amount);
balance = balance - amount;
Console.WriteLine("Balance after Withdrawal : " + balance);
return amount;
}
else
{
return 0; // transaction rejected
}
}
}
public void DoTransactions()
{
for (int i = 0; i < 100; i++)
{
Withdraw(r.Next(1, 100));
}
}
}
class Test
{
static void Main()
{
Thread[] threads = new Thread[10];
Account acc = new Account(1000);
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(new ThreadStart(acc.DoTransactions));
threads[i] = t;
}
for (int i = 0; i < 10; i++)
{
threads[i].Start();
}
}
}
In general, avoid locking on a public type, or instances beyond your code's control. The common constructs lock (this), lock (typeof (MyType)), and lock ("myLock") violate this guideline:
- lock (this) is a problem if the instance can be accessed publicly.
- lock (typeof (MyType)) is a problem if MyType is publicly accessible.
- lock("myLock") is a problem since any other code in the process using the same string, will share the same lock.
Best practice is to define a private object to lock on, or a private static object variable to protect data common to all instances.