说到lock锁,我信赖在座的列位没有不会用的,而且还知道怎么用不会失足,但让他们聊一聊为什么可以锁住,都说人以群分,也许就有了下面低中高水平的三类人吧。

第一类人

将lock工具界说成static,这样就能让多个线程看到同一个工具,以此实现线程间互斥和保证同步,若是再深问为什么?就怕遮遮掩掩的说似乎每个实例都有一个同步块索引,再睁开的话就顶不住了,横竖人人都这么写,我也不敢问,我也不会说,若是上代码,只能这样丢给你。

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);
    }
}

第二类人

这类人可能看过CLR via C# 这样类似圣经级著作,而且对相关看法也比较清晰。

1. 清晰‘引用类型’ 在堆上的结构结构及栈上的指针是指向方式表索引(类型工具指针),如下图。

2. 清晰当lock住工具后,它的‘同步块索引’ 和 CLR上的‘同步块数组’是出现一个关联关系,然后又是一张图。

牛X点:仅仅用了两张图就把这个事情解决的相当完善,读者一看就明了了,然来是每个线程在lock的时刻会查看一下工具的同步块索引所映射的同步块数组中的坑中信息来判断是否可以加锁。

不足点:一定要挑刺的话,那就是这类人只是在听别人讲故事,到底是不是真的云云实在自己心里也没谱,只是一味的信赖对方的人格魅力,而真正的人,十句话中只有一句假话

第三类人

这类人就会动用资源或者人脉亲自实验一下是不是如第二类人所形貌的那样,操刀的话,最好的工具就是windbg,接下来我就操刀一把。

1. 对‘引用类型’结构结构的弥补

现在人人也知道了每个工具都有两个分外开销,就是‘同步块索引’ + '方式表索引',在x86系统中,每个索引各占4字节,而在x64系统中,每个索引各占8字节,因我的系统是x64,根据x64版本测试。

2. 案例代码

有了上面的知识弥补,接下来我开两个task,在task中举行lock操作。

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

            Console.WriteLine("步骤一:lock前!!!");
            Console.ReadLine();

            var task1 = Task.Factory.StartNew(() =>
            {
                lock (employee)
                {
                    Console.WriteLine("步骤二:lock1中。。。。");
                    Console.ReadLine();
                }
                Console.WriteLine("步骤二:退出lock1...");
            });

            var task2 = Task.Factory.StartNew(() =>
            {
                lock (employee)
                {
                    Console.WriteLine("步骤二:lock2中。。。。");
                    Console.ReadLine();
                }
                Console.WriteLine("步骤二:退出lock2...");
            });

            Task.WaitAll(task1, task2);
            Console.WriteLine("步骤三:lock后,所有退出!");
            Console.ReadLine();
        }
    }

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

3. 使用windbg调试

我准备分三步骤实现,lock前,lock中,lock后,然后拿到这三种情形下的dump文件来展示 employee 工具的同步块索引 和 CLR全局同步块数组实时情形。

<1 style="box-sizing: border-box;"> lock前

先把程序跑起来,再从义务管理器中天生dump文件。

!threads -> ~0s -> !clrstack -l 这三个下令是为了寻找主线程栈上的局部变量 employee 的内存地址。

,

欧博app下载

欢迎进入欧博app下载网站:www.aLLbetgame.us,欧博app下载网站是欧博官方网站。欧博app下载网站开放欧博注册、欧博代理、欧博电脑客户端、欧博app下载等业务。

,
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]

从最后的LOCALS中可以看到,当前主线程有三个局部变量,依次是:employee,task1,task2,而其中的 0x0000023523f72dc0 就是employee。

!dumpobj 0x0000023523f72dc0 -> !dumpobj 0000023523f72dd8 找到 employee 在堆上的内存区域

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

使用菜单 view -> memory 查看 0000023523f72dd8 在堆上的结构,从图上看找的没有错哈。

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............

从上面看到,00000235`23f72dd8行的前8个字节就是employee的同步块索引,此时所有是0,好的,纪录一下这个状态。

<2 style="box-sizing: border-box;"> lock中

继续在控制台按Enter,从图中可以看到lock1获取到了锁。

使用view -> memory 查看 0000023523f72dd8 内存索引地址,可以看到由原来的全0变成了 0000000007000008,如下图。


然后用 !syncblk -all 把CLR的全局同步块数组调出来,看看是不是占了一个坑位。

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

看到最后一行了没?ConsoleApp2.Employee 占用的坑位编号是7,说明 0000000007000008 和这个 7 做了关联,同时MonitorHeld=3也说明当前有一个持有线程(+1),有一个守候线程(+2),以是这个看法也得到了验证。

<3 style="box-sizing: border-box;"> lock后

继续在控制台Enter,从图中可以看到两个lock都已经竣事了。看此时employee会怎样?

然后照样一样查看 0000023523f72dd8 的内存结构情形。

不外奇怪的是工具的同步块索引并没有变,继续查看同步块数组。

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

从各项都是0来看,它已经处于初始化状态了,MonitorHeld=0也示意当前无线程持有ConsoleApp2.Employee,关于工具同步块索引没有变以及数组中的坑位,可能会被CLR后期惰性删除和初始化吧,谁知道呢?

总结

貌似跟踪下来和CLR via C#说的不是那么一致,若是我是对的,那就是重大发现,若是是错的,那就是水平有限,开个玩笑,可能新版本在底层做了进一步优化吧。