How Expensive Is the Lock Statement

How expensive is the lock statement?

Here is an article that goes into the cost. Short answer is 50ns.

How expensive is lock(...) when the lock isn't contended?

We can test it...

I get:

1000000000; 2164 (no lock)
1000000000; 23258 (lock)
21.094ns per lock

Code:

using System;
using System.Diagnostics;

static class P
{
static void Main()
{

Test(1); // for JIT
Test(1000000);
}
static readonly object syncLock = new object();
static void Test(int count)
{
int j = 0;
var watch = Stopwatch.StartNew();
for(int i = 0 ; i < count ; i++)
{
for (int z = 0; z < 1000; z++)
j++;
}
watch.Stop();
long withoutMillis = watch.ElapsedMilliseconds;
Console.WriteLine("{0}; {1} (no lock)", j, watch.ElapsedMilliseconds);

j = 0;
watch = Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
for (int z = 0; z < 1000; z++ )
lock (syncLock)
{
j++;
}
}
watch.Stop();
long withMillis = watch.ElapsedMilliseconds;
Console.WriteLine("{0}; {1} (lock)", j, watch.ElapsedMilliseconds);

long deltaNano = (withMillis - withoutMillis) * 1000000;
// nano = 1000 micro = 1000000 milli
double perLockNano = deltaNano/(1000.0 * count);
Console.WriteLine("{0}ns per lock", perLockNano);
}
}

What is the reason for locks are an expensive operation to be uttered so often?

Is it the fact that it causes a LOCK# instruction to be emitted at Assembler level?

No, since it doesn't always do that.

Is it the fact that obtaining a lock requires a kernel call into the OS?

No, since it typically doesn't do that.

In fact, locks are very, very inexpensive. It's contention that's expensive. If you have to choose between a lock and contention, most of the time the lock is a better option.

Locks, when used properly, are a contention avoidance mechanism. They automatically find threads that contend and de-schedule them such that one winds up primarily with threads that do not contend running concurrently.

For example: Say you have four threads that are ready to run, A, B, C, and D. Say A and B contend with each other (say they manipulate the same collection). And say C and D contend with each other, but A doesn't contend with C. If A and B are running at the same time (contending), the locks will cause one of them to not be ready to run, the scheduler will then schedule C (or D), and the two threads will run without further contention. (At least until the next context switch.)

Usually, when people say "locks are expensive", they mean that contention is expensive. Unfortunately, by phrasing it the way they do, they often encourage people to minimize locks but increase contention in the process. That is a losing proposition in the vast majority of cases. (There are a few exceptions.)

What does a lock statement do under the hood?

The lock statement is translated by C# 3.0 to the following:

var temp = obj;

Monitor.Enter(temp);

try
{
// body
}
finally
{
Monitor.Exit(temp);
}

In C# 4.0 this has changed and it is now generated as follows:

bool lockWasTaken = false;
var temp = obj;
try
{
Monitor.Enter(temp, ref lockWasTaken);
// body
}
finally
{
if (lockWasTaken)
{
Monitor.Exit(temp);
}
}

You can find more info about what Monitor.Enter does here. To quote MSDN:

Use Enter to acquire the Monitor on
the object passed as the parameter. If
another thread has executed an Enter
on the object but has not yet executed
the corresponding Exit, the current
thread will block until the other
thread releases the object. It is
legal for the same thread to invoke
Enter more than once without it
blocking; however, an equal number of
Exit calls must be invoked before
other threads waiting on the object
will unblock.

The Monitor.Enter method will wait infinitely; it will not time out.



Related Topics



Leave a reply



Submit