說到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> 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,好的,記錄一下這個狀態,
<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#說的不是那么一致,如果我是對的,那就是重大發現,如果是錯的,那就是水平有限??????,開個玩笑,可能新版本在底層做了進一步優化吧,
好了,本篇就說到這里,希望對你有幫助
如您有更多問題與我互動,掃描下方進來吧~


轉載請註明出處,本文鏈接:https://www.uj5u.com/net/50938.html
標籤:C#
下一篇:求助
