A lock lock can distinguish the low, medium and high level programmers' ways to deal with problems

Keywords: C# Windows

When it comes to lock lock, I'm sure you will not use it if you don't have it, and you know how to use it correctly. But let them talk about why they can lock it. They all say that people are divided into groups. There are probably three types of people with low, medium and high levels.

Group 1

Define the lock object as static, so that multiple threads can see the same object, so as to realize mutual exclusion and synchronization between threads. If you ask why? I'm afraid that it seems that every instance has a synchronization block index. If it is expanded again, it won't be able to resist it. Anyway, everyone writes like this. I don't dare to ask. I won't say that if it's code, it can only be left to you like this.

    public class Program
    {
        public static object lockMe = new object();

        public static void Main(string[] args)
        {
            var task1 = Task.Factory.StartNew(() =>
            {
                lock (lockMe)
                {
                    //todo
                }
            });

            var task2 = Task.Factory.StartNew(() =>
            {
                lock (lockMe)
                {
                    //todo
                }
            });

            Task.WaitAll(task1, task2);
        }
    }

Second kinds of people

This kind of people may have read similar biblical works like CLR via C Chen, and they also have a clear understanding of relevant concepts.

1. Make it clear that the layout structure of reference type on the heap and the pointer on the stack point to the method table index (type object pointer), as shown in the following figure.

2. It is clear that when an object is locked, its' synchronization block index 'and' synchronization block array 'on the CLR represent an association relationship, and then another graph.

Niux point: only two graphs are used to solve this problem perfectly. Readers can see it at a glance. However, when each thread is locked, it will check the pit information in the synchronized block array mapped by the synchronized block index of the object to determine whether it can be locked.

Disadvantages: we must be picky, that is, such people are just listening to other people's stories, whether it is true or not, in fact, they have no music in their hearts, just blindly believe in each other's personality charm, and really Chinese style There is only one falsehood in ten sentences~ To be careful

Third kinds of people

This kind of person will use resources or contacts to try to see if the best tool is windbg, as described by the second kind of person. Next, I will do it.

1. Supplement to the layout structure of "reference type"

Now you also know that each object has two additional overhead, namely 'synchronous block index' + 'method table index'. In x86 system, each index accounts for 4 bytes, while in x64 system, each index accounts for 8 bytes, because my system is x64, tested according to x64 version.

2. Case code

With the above knowledge supplement, next I open two tasks to perform the lock operation in the task.

namespace ConsoleApp2
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var employee = new Employee();

            Console.WriteLine("Step one: lock Front!!!");
            Console.ReadLine();

            var task1 = Task.Factory.StartNew(() =>
            {
                lock (employee)
                {
                    Console.WriteLine("Step two: lock1 In....");
                    Console.ReadLine();
                }
                Console.WriteLine("Step 2: exit lock1...");
            });

            var task2 = Task.Factory.StartNew(() =>
            {
                lock (employee)
                {
                    Console.WriteLine("Step two: lock2 In....");
                    Console.ReadLine();
                }
                Console.WriteLine("Step 2: exit lock2...");
            });

            Task.WaitAll(task1, task2);
            Console.WriteLine("Step three: lock After, all exit!");
            Console.ReadLine();
        }
    }

    public class Employee
    {
        public int a = 1;
        public int b = 2;
    }
}

3. Debugging with windbg

I'm going to implement it in three steps: before lock, in lock, and after lock. Then I get the dump files in these three situations to show the real-time situation of the synchronization block index of the employee object and the CLR global synchronization block array.

Before <1> lock

Run the program first, and then generate the dump file from the task manager.

! threads - > ~ 0s - >.

0:000> !threads
ThreadCount:      2
UnstartedThread:  0
BackgroundThread: 1
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                                                        Lock  
       ID OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1 40b8 00000235222457f0    2a020 Preemptive  0000023523F76D00:0000023523F77FD0 000002352223b0f0 1     MTA 
   6    2 44c8 00000235222705f0    2b220 Preemptive  0000000000000000:0000000000000000 000002352223b0f0 0     MTA (Finalizer) 
0:000>  ~0s
ntdll!ZwReadFile+0x14:
00007ffa`bd7baa64 c3              ret
0:000> !clrstack -l  
OS Thread Id: 0x40b8 (0)
        Child SP               IP Call Site
0000005f721fe748 00007ffabd7baa64 [InlinedCallFrame: 0000005f721fe748] Microsoft.Win32.Win32Native.ReadFile(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
0000005f721fe748 00007ffaa5d7b7e8 [InlinedCallFrame: 0000005f721fe748] Microsoft.Win32.Win32Native.ReadFile(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
0000005f721fe710 00007ffaa5d7b7e8 *** ERROR: Module load completed but symbols could not be loaded for mscorlib.ni.dll
DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)

0000005f721fe7f0 00007ffaa65920cc System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean, Boolean, Int32 ByRef)
    LOCALS:
        <no data>
        <no data>
        <no data>
        <no data>
        <no data>
        <no data>

0000005f721fe880 00007ffaa6591fd5 System.IO.__ConsoleStream.Read(Byte[], Int32, Int32)
    LOCALS:
        <no data>
        <no data>

0000005f721fe8e0 00007ffaa5d470f4 System.IO.StreamReader.ReadBuffer()
    LOCALS:
        <no data>
        <no data>

0000005f721fe930 00007ffaa5d47593 System.IO.StreamReader.ReadLine()
    LOCALS:
        <no data>
        <no data>
        <no data>
        <no data>

0000005f721fe990 00007ffaa6738b0d System.IO.TextReader+SyncTextReader.ReadLine()

0000005f721fe9f0 00007ffaa6530d98 System.Console.ReadLine()

0000005f721fea20 00007ffa485d0931 *** WARNING: Unable to verify checksum for ConsoleApp2.exe
ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 19]
    LOCALS:
        0x0000005f721feaa8 = 0x0000023523f72dc0
        0x0000005f721feaa0 = 0x0000000000000000
        0x0000005f721fea98 = 0x0000000000000000

0000005f721fecb8 00007ffaa7af6c93 [GCFrame: 0000005f721fecb8] 

As can be seen from the final LOCALS, the current main thread has three local variables: employee, task1, task2, and 0x0000023523f72dc0 is employee.

! dumpobj 0x0000023523f72dc0 - >

0:000>  !dumpobj 0x0000023523f72dc0
Name:        ConsoleApp2.Program+<>c__DisplayClass0_0
MethodTable: 00007ffa484c5af8
EEClass:     00007ffa484c2600
Size:        24(0x18) bytes
File:        C:\dream\Csharp\ConsoleApp1\ConsoleApp2\bin\x64\Debug\ConsoleApp2.exe
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffa484c5bb8  4000003        8 ConsoleApp2.Employee  0 instance 0000023523f72dd8 employee
0:000> !dumpobj 0000023523f72dd8 
Name:        ConsoleApp2.Employee
MethodTable: 00007ffa484c5bb8
EEClass:     00007ffa484c2678
Size:        24(0x18) bytes
File:        C:\dream\Csharp\ConsoleApp1\ConsoleApp2\bin\x64\Debug\ConsoleApp2.exe
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffaa57685a0  4000001        8         System.Int32  1 instance                1 a
00007ffaa57685a0  4000002        c         System.Int32  1 instance                2 b


Use the menu view - > memory to view the layout of 0000023523f72dd8 on the heap. There is no mistake in the figure.

00000235`23f72dc8 d8 2d f7 23 35 02 00 00 00 00 00 00 00 00 00 00  .-.#5...........
00000235`23f72dd8 b8 5b 4c 48 fa 7f 00 00 01 00 00 00 02 00 00 00  .[LH............

As you can see from the above, the first 8 bytes of the 00000235`23f72dd8 line are the employee's synchronization block index. At this time, they are all 0. OK, record the status.

<2> lock

Continue to press Enter in the console, and you can see from the figure that lock1 has acquired the lock.

Using View - > memory to view the memory index address of 0000023523f72dd8, you can see that it has changed from 0 to 0000000007000008, as shown below.

Then use! syncblk -all to call out the global synchronization block array of CLR to see if it occupies a pit.

0:006> !syncblk -all
Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner
    1 00000235222af108            0         0 0000000000000000     none    0000023523f77150 System.__ComObject
    2 00000235222af158            0         0 0000000000000000     none    0000023523f77170 System.EventHandler`1[[Windows.Foundation.Diagnostics.TracingStatusChangedEventArgs, mscorlib]]
    3 00000235222af1a8            0         0 0000000000000000     none    0000023523f771b0 Windows.Foundation.Diagnostics.TracingStatusChangedEventArgs
    4 00000235222af1f8            0         0 0000000000000000     none    0000023523f79458 Microsoft.Win32.UnsafeNativeMethods+ManifestEtw+EtwEnableCallback
    5 00000235222af248            0         0 0000000000000000     none    0000023523f7a158 Microsoft.Win32.UnsafeNativeMethods+ManifestEtw+EtwEnableCallback
    6 00000235222af298            0         0 0000000000000000     none    0000023523f7a2f8 System.Object
    7 00000235222af2e8            3         1 00000235222cb320 56a8   6   0000023523f72dd8 ConsoleApp2.Employee
-----------------------------
Total           7
CCW             1
RCW             2
ComClassFactory 0
Free            0


See the last line? The number of the pit occupied by ConsoleApp2.Employee is 7, indicating that 0000000007000008 is related to this 7. At the same time, monitorhold = 3 also indicates that there is a holding thread (+ 1) and a waiting thread (+ 2). Therefore, this view has also been verified.

After <3> lock

Continue to Enter on the console, and you can see from the figure that both lock s have ended. See what happens to the employee at this time?

Then check the memory layout of 00000 23523f72dd8.

However, the strange thing is that the synchronization block index of the object has not changed. Continue to view the synchronization block array.

0:000> !syncblk -all
Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner
    1 00000235222af108            0         0 0000000000000000     none    0000023523f77150 System.__ComObject
    2 00000235222af158            0         0 0000000000000000     none    0000023523f77170 System.EventHandler`1[[Windows.Foundation.Diagnostics.TracingStatusChangedEventArgs, mscorlib]]
    3 00000235222af1a8            0         0 0000000000000000     none    0000023523f771b0 Windows.Foundation.Diagnostics.TracingStatusChangedEventArgs
    4 00000235222af1f8            0         0 0000000000000000     none    0000023523f79458 Microsoft.Win32.UnsafeNativeMethods+ManifestEtw+EtwEnableCallback
    5 00000235222af248            0         0 0000000000000000     none    0000023523f7a158 Microsoft.Win32.UnsafeNativeMethods+ManifestEtw+EtwEnableCallback
    6 00000235222af298            0         0 0000000000000000     none    0000023523f7a2f8 System.Object
    7 00000235222af2e8            0         0 0000000000000000     none    0000023523f72dd8 ConsoleApp2.Employee
    8 00000235222af338            0         0 0000000000000000     none    0000023523f76750 System.IO.TextWriter+SyncTextWriter
-----------------------------
Total           8
CCW             1
RCW             2
ComClassFactory 0
Free            0


From the point of view that each item is 0, it is already in the initialization state. Monitorhold = 0 also means that the current wireless program holds ConsoleApp2.Employee. As for the object synchronization block index and the pit in the array, it may be deleted and initialized by the later CLR laziness. Who knows?

summary

It seems that the tracking is not so consistent with what CLR via C Chen said. If I am right, it is a major discovery. If I am wrong, it is a limited level To be careful , I'm kidding. Maybe the new version has been further optimized at the bottom.

Well, that's all. I hope it can help you

If you have more questions to interact with me, please come in under the scan~

Posted by chefou on Thu, 16 Apr 2020 09:10:34 -0700