一個lock鎖就能夠分出低中高水平的程序員對問題的處置方式

說到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操做。spa

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> lock前

先把程序跑起來,再從任務管理器中生成dump文件。指針

!threads -> ~0s -> !clrstack -l 這三個命令是爲了尋找主線程棧上的局部變量 employee 的內存地址。
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,好的,記錄一下這個狀態。code

<2> 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> 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#說的不是那麼一致,若是我是對的,那就是重大發現,若是是錯的,那就是水平有限😄😄😄,開個玩笑,可能新版本在底層作了進一步優化吧。

好了,本篇就說到這裏,但願對你有幫助

相關文章
相關標籤/搜索