Finally, the
RTM version of .NET 4 and Visual Studio 2010 came out. The final optimizations in the final version of the platform have been carried out and can be safely tested.
It's no secret that one of the major innovations of .NET 4 is
Parallel Extensions - a set of tools to facilitate code parallelization and work in a multithreaded environment. Among other tools of this set there are synchronization primitives, which have also been processed.
In particular, a modified version of the highly popular primitive
ManualResetEvent . For those who are not very familiar with this tool: with its help you can synchronize the execution of code sections running in different threads. The object can be in 2 states - fixed and uninstalled. The transition from one to another is performed using the Set () and Reset () methods. In a nutshell, how it works (here mre is an instance of type ManualResetEvent):
Stream 1 | Stream 2 | Time |
---|
mre.Reset (); mre.WaitOne (); | // code execution | 0 |
//expectation | // code execution | one |
//expectation | // code execution | 2 |
//expectation | // code execution | 3 |
//expectation | mre.Set (); | four |
// code execution | // ... | five |
An improved version of this primitive from .NET 4 is called
ManualResetEventSlim . The basic idea is to reduce the overhead in case only 1 thread is accessing the primitive. Used so-called. A “hybrid scheme” that can be implemented like this:
internal sealed class SimpleHybridLock : IDisposable
{
private Int32 m_waiters = 0;
private AutoResetEvent m_waiterLock = new AutoResetEvent( false );
public void Enter()
{
if (Interlocked.Increment( ref m_waiters) == 1)
return ;
m_waiterLock.WaitOne();
}
public void Leave()
{
if (Interlocked.Decrement( ref m_waiters) == 0)
return ;
m_waiterLock.Set();
}
public void Dispose()
{
m_waiterLock.Dispose();
}
}
* This source code was highlighted with Source Code Highlighter .
This is an example from Richter’s book
“CLR via C #”, 3rd edition . A primitive SimpleHybridLock has a couple of public methods Enter () and Leave (). Calling these methods should frame a critical section of our code that we always want to execute in only one thread. The class code is quite transparent: the first thread that called Enter () increases the internal counter by 1. The second thread also increases the counter, while blocking until someone calls Set () on the m_waiterLock object. So if there is no competitive access to the primitive, the very WaitOne () and Set () methods, which are very heavy in terms of performance, will not be called. This can positively affect the speed of the code.
A similar principle is built and ManualResetEventSlim. I think there are more intelligent mechanisms, such as control of recursive calls, etc. As an end user of the platform, I was interested in the real performance difference between ManualResetEvent and its * -Slim version. To find it, I prepared a small “benchmark”. This is a console application of this type:
static void Main( string [] args)
{
ManualResetEventSlim mres = new ManualResetEventSlim( false );
ManualResetEventSlim mres2 = new ManualResetEventSlim( false );
ManualResetEvent mre = new ManualResetEvent( false );
long total = 0;
int COUNT = 50;
for ( int i = 0; i < COUNT; i++)
{
mres2.Reset();
//
Stopwatch sw = Stopwatch.StartNew();
//
ThreadPool.QueueUserWorkItem((obj) =>
{
//Method(mres, true);
Method2(mre, true );
mres2.Set();
});
//
//Method(mres, false);
Method2(mre, false );
//,
mres2.Wait();
sw.Stop();
Console .WriteLine( "Pass {0}: {1} ms" , i, sw.ElapsedMilliseconds);
total += sw.ElapsedMilliseconds;
}
Console .WriteLine();
Console .WriteLine( "===============================" );
Console .WriteLine( "Done in average=" + total / ( double )COUNT);
Console .ReadLine();
}
// ManualResetEventSlim
private static void Method(ManualResetEventSlim mre, bool value )
{
//
for ( int i = 0; i < 9000000; i++)
{
if ( value )
{
mre.Set();
}
else
{
mre.Reset();
}
}
}
// ManualResetEvent
private static void Method2(ManualResetEvent mre, bool value )
{
//
for ( int i = 0; i < 9000000; i++)
{
if ( value )
{
mre.Set();
}
else
{
mre.Reset();
}
}
}
}
* This source code was highlighted with Source Code Highlighter .
In the Main () method, we create instances of primitives and model access to them from 2 threads - the main thread and the pool thread. In this case, the pool thread will set a state in a loop, and the main thread will reset. Repeat the COUNT experiment once and display the average value on the screen. This is what happened on my laptop (2-core T7250 CPU, Win 7 x64):
')
ManualResetEvent | ManualResetEvent Slim |
 |  |
The difference is obvious and quite significant - about 10 times.
So Preferred is the use of ManualResetEventSlim, since it is not always when calling Set () and Reset () there will be a long call to the Windows kernel objects and you can win a lot of money in speed;)