Thread Locking
Exclusive locking
in threading ensures that one thread
does not enter a critical section while another thread is in the critical
section of code. If another thread attempts to enter a locked code, it will wait
(block) until the object is released. To achieve this functionality we have to
main exclusive locking constructs are
The lock construct is faster and
more convenient. Mutex, though, has a place in that its lock can span
applications in different processes on the computer.
The lock statement takes the following form:
lock(expression)
statement_block
Where
expression :
Specifies the object that you want to lock on. expression must
be a reference type.
statement_block
:The statements of the critical section.
A critical
section is
a piece of code that accesses a
shared resource (data structure or device)
but the condition is that only one thread can enter in this section in a time.
In
the
following example we implement threads and lock.
As long as the lock statement
is present, the statement block is a critical section and Salary will
never become a negative number.
using
System;
using
System.Threading;
using
System.Diagnostics;
namespace
Threadlocking
{
class Department
{
private
Object thisLock = new
Object();
int salary;
Random r =
new Random();
public Department(int
initial)
{
salary = initial;
}
int Withdraw(int
amount)
{
// This condition never is true unless
the lock statement
// is commented out.
if (salary < 0)
{
throw
new Exception("Negative
Balance");
}
// Comment out the next line to see the
effect of leaving out
// the lock keyword.
lock (thisLock)
{
if (salary >= amount)
{
Console.WriteLine("salary
before Withdrawal : " + salary);
Console.WriteLine("Amount
to Withdraw : -" + amount);
salary = salary - amount;
Console.WriteLine("salary
after Withdrawal : " + salary);
return amount;
}
else
{
return 0;
// transaction rejected
}
}
}
public void
DoTransactions()
{
for (int
i = 0; i < 100; i++)
{
Withdraw(r.Next(1, 100));
}
}
}
class Process
{
static void
Main()
{
Thread[] threads =
new Thread[10];
Department dep =
new Department(1000);
for (int
i = 0; i < 10; i++)
{
Thread t =
new Thread(new
ThreadStart(dep.DoTransactions));
threads[i] = t;
}
for (int
i = 0; i < 10; i++)
{
threads[i].Start();
}
Console.Read();
}
}
}
Output :
In general, we avoid locking
because
lock (this) is
a problem if the instance can be accessed publicly.
Nested Locking
A thread can repeatedly lock the
same object in a nested (reentrant) fashion:
lock
(locker)
lock (locker)
lock (locker)
{//
Do something...
}
In the nested locking, the object is unlocked only
when the outermost lock statement has exited. It is useful when one method calls
another within a lock:
using
System;
using
System.Threading;
using
System.Diagnostics;
namespace
nestedlocking
{
class Program
{
static object
x = new object();
static void
AnotherMethod()
{
lock (x) {
Console.WriteLine("Another
method"); }
}
static void
Main()
{
lock (x)
{
AnotherMethod();
// We still have the lock - because
locks are reentrant.
}
Console.Read();
}
}
}
Output:
The problem
with threading
Lock-based resource protection and thread/process synchronization have many
disadvantages:
-
They cause blocking, which means some threads/processes have to wait until a
lock (or a whole set of locks) is released.
-
Lock handling adds overhead for each access to a resource, even when the
chances for collision are very rare. (However, any chance for such
collisions is a
race condition).
-
Lock contention limits scalability and adds complexity.
-
Priority Inversion
High priority threads/processes cannot proceed, if a low priority
thread/process is holding the common lock.
-
Convoying. All other threads have to wait, if a thread holding a lock is
descheduled due to a time-slice interrupt or page fault.
-
Hard to debug: Bugs associated with locks are time dependent. They are
extremely hard to replicate.