Implementing SyncLock using simpler language elements.

The other day I had to answer various questions about SyncLock.

 

What is really locked when you do SyncLock?

Why after doing SyncLock X, you can modify X and even assign to it a different value.

What are the performance implications of SyncLock?

 

After spending some time trying to answer SyncLock questions one after another, I figured that it would be easier just implement SyncLock using some simpler elements and leave the analysis of the code to ones who interested.

 

Let’s take a look at what compiler produces for this code:

 

======================================================= 

 

Module Module1

    Sub Main()

        Dim o As New C1

        SyncLock o

            ' inside the SyncLock

        End SyncLock

    End Sub

End Module

Class C1

End Class

 

======================================================= 

 

Here is what we can see in ildasm when we open the exe and go to the Main (most interesting parts are blue).

 

=======================================================

.method public static void  Main() cil managed

{

  .entrypoint

  .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )

  // Code size       31 (0x1f)

  .maxstack  1

  .locals init ([0] class ConsoleApplication4.C1 o,

           [1] class ConsoleApplication4.C1 _Vb_t_ref_0)

  IL_0000:  nop

  IL_0001:  newobj instance void ConsoleApplication4.C1::.ctor()

  IL_0006:  stloc.0

  IL_0007:  ldloc.0

  IL_0008:  stloc.1

  IL_0009:  ldloc.1

  IL_000a:  call void [mscorlib]System.Threading.Monitor::Enter(object)

  IL_000f:  nop

  IL_0010:  nop

  .try

  {

    IL_0011:  leave.s    IL_001c

  }  // end .try

  finally

  {

    IL_0013:  nop

    IL_0014:  ldloc.1

    IL_0015:  call void [mscorlib]System.Threading.Monitor::Exit(object)

    IL_001a:  nop

    IL_001b:  endfinally

  }  // end handler

  IL_001c:  nop

  IL_001d:  nop

  IL_001e:  ret

} // end of method Module1::Main

 

=======================================================

 

Note that there is quite a bit of action going on when we use simple SyncLock. Implementation of simple SyncLock appears to be quite larger than it is in the initial source code.

First note that compiler creates a temporary variable _Vb_t_ref_0 where it stores the lock object reference  (o in our case) and then calls System.Threading.Monitor.Enter with this object.

The second interesting thing is the Try/Finally block.To make sure the object gets unlocked when code leaves SyncLock, compiler makes synthetic Try/Finally block and in the Finally it does System.Threading.Monitor.Exit with _Vb_t_ref_0   

 

An interesting experiment would be creating VB code that produces similar IL. As you see a little sample is better that any description:

 

=======================================================

Module Module1

    Sub Main()

        Dim o As New C1

        Dim _Vb_t_ref_0 As C1 = o

        System.Threading.Monitor.Enter(_Vb_t_ref_0)

        Try

            ' inside the SyncLock

        Finally

            System.Threading.Monitor.Exit(_Vb_t_ref_0)

        End Try

    End Sub

End Module

Class C1

End Class

 

======================================================= 

 

To prove that this is how SyncLock works, let’s take a look at the IL by opening this in ildasm. As you see the IL for Main is almost identical to what we had before. The only difference is that  _Vb_t_ref_0 and o have swapped positions as local[0] and local[1], but this does not make any difference to the nature of the code. :

 

=======================================================

.method public static void  Main() cil managed

{

  .entrypoint

  .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )

  // Code size       31 (0x1f)

  .maxstack  1

  .locals init ([0] class ConsoleApplication4.C1 _Vb_t_ref_0,

           [1] class ConsoleApplication4.C1 o)

  IL_0000:  nop

  IL_0001:  newobj instance void ConsoleApplication4.C1::.ctor()

  IL_0006:  stloc.1

  IL_0007:  ldloc.1

  IL_0008:  stloc.0

  IL_0009:  ldloc.0

  IL_000a:  call void [mscorlib]System.Threading.Monitor::Enter(object)

  IL_000f:  nop

  IL_0010:  nop

  .try

  {

    IL_0011:  leave.s    IL_001c

  }  // end .try

  finally

  {

    IL_0013:  nop

    IL_0014:  ldloc.0

    IL_0015:  call void [mscorlib]System.Threading.Monitor::Exit(object)

    IL_001a:  nop

    IL_001b:  endfinally

  }  // end handler

  IL_001c:  nop

  IL_001d:  nop

  IL_001e:  ret

} // end of method Module1::Main