vb.net2019-多线程并行计算(3)
接上節,我們可以使用下面語句創建一個線程本地變量,利用靜態TLS功能
Dim betterCounter As ThreadLocal(Of Integer) = New ThreadLocal(Of Integer)(Function() 1)
betterCounter的值初始化為1。在本程序中,jg被初始化為50,并定義成線程本地變量
Dim jg As ThreadLocal(Of Integer) = New ThreadLocal(Of Integer)(Function() 50)然后,我們使用jg.Value 來讀寫這個本地變量的值
jg.Value -= mynum
三、動態TLS
Imports System
Imports System.Threading
Module Module1
Sub Main()Dim mythread1 As ThreadDim mythread2 As ThreadDim mythread3 As Thread'創建線程對象mythread1 = New Thread(AddressOf mythreadrun)mythread2 = New Thread(AddressOf mythreadrun)mythread3 = New Thread(AddressOf mythreadrun)Console.WriteLine(Now.ToLongTimeString & "線程對象創建完畢,開始執行線程")'執行線程mythread1.Start("線程1")mythread2.Start("線程2")mythread3.Start("線程3")'等待線程完成mythread1.Join()mythread2.Join()mythread3.Join()'線程執行完畢Console.WriteLine(Now.ToLongTimeString & "線程執行完畢!") End Sub Public Sub mythreadrun(ByVal data As Object)Dim mynum As Integer'分配一個新的槽,這個槽存放線程本地數據,槽名稱'必須唯一Thread.AllocateNamedDataSlot(data)Dim jg As LocalDataStoreSlotjg = Thread.GetNamedDataSlot(data)Thread.SetData(jg, 100)TryFor mynum = 1 To 10Thread.SetData(jg, Thread.GetData(jg) - mynum)Console.WriteLine(data & " " & Now.ToLongTimeString & "=>" & (Thread.GetData(jg) + mynum) & "-" & mynum & ",計算結果為:" & Thread.GetData(jg))Thread.Sleep(2)NextCatchConsole.WriteLine(data & " " & Now.ToLongTimeString & "線程異常終止!")'終止線程Thread.CurrentThread.Abort()FinallyThread.FreeNamedDataSlot(data)End Try End SubEnd Module
運行結果如我們所愿,jg變量通過動態TLS提供的槽機制實現了線程本地變量
[點擊并拖拽以移動]
'分配一個新的槽,這個槽存放線程本地數據,槽名稱為'myjg,名稱必須唯一Thread.AllocateNamedDataSlot(data)Dim jg As LocalDataStoreSlotjg = Thread.GetNamedDataSlot(data)Thread.SetData(jg, 100)以上代碼是關健,我們使用命名數據槽,當然,我們也可以使用未命名槽,因為未命名數據槽相對較簡單,所以這里使用了命名數據槽,向大家演示一下其功能。
注意:
如果使用 AllocateNamedDataSlot 方法已分配已經存在的指定名稱的槽,此方法會引發異常,且無法測試是否已分配某個槽。另外,使用此方法分配的數據槽必須使用 FreeNamedDataSlot 來釋放。
本例中,我們分配槽使用下面語句
Thread.AllocateNamedDataSlot(data)
獲取某個命名槽的引用,以便進行下一步操作
jg = Thread.GetNamedDataSlot(data)
Thread.SetData和Thread.GetData可寫、讀槽中數據
四、數據槽的值在線程或上下文對象之間不共享
LocalDataStoreSlot 結構可用作本地存儲內存機制,線程和上下文可以使用此機制分別存儲線程特定的數據和上下文特定的數據。 公共語言運行時在創建每個進程時給它分配一個多槽數據存儲區數組。 線程或上下文調用各種函數在數據存儲區中分配數據槽、在槽內存儲和檢索數據值、以及釋放數據槽以便在線程或上下文過期后重新使用它。
對于每個線程或上下文,數據槽都是唯一的;它們的值在線程或上下文對象之間不共享。 數據槽可根據名稱或根據索引號來分配。
我們可以從下面程序看出
Imports System Imports System.ThreadingModule Module1Sub Main()Dim mythread1 As ThreadDim mythread2 As ThreadDim mythread3 As ThreadDim jg As LocalDataStoreSlot'創建線程對象mythread1 = New Thread(AddressOf mythreadrun)mythread2 = New Thread(AddressOf mythreadrun)mythread3 = New Thread(AddressOf mythreadrun)Console.WriteLine(Now.ToLongTimeString & "線程對象創建完畢,開始執行線程")'jg = Thread.AllocateNamedDataSlot("myjg")' Thread.SetData(jg, 100)'執行線程mythread1.Start("線程1")mythread2.Start("線程2")mythread3.Start("線程3")'等待線程完成mythread1.Join()mythread2.Join()mythread3.Join()'線程執行完畢Console.WriteLine(Now.ToLongTimeString & "線程執行完畢!")Thread.FreeNamedDataSlot("myjg")End SubPublic Sub mythreadrun(ByVal data As Object)Dim randomGenerator As New Random()Dim mynum As Integer'分配一個新的槽,這個槽存放線程本地數據,槽名稱為 'myjg Dim jg As LocalDataStoreSlotTryjg = Thread.AllocateNamedDataSlot("myjg")Catchjg = Thread.GetNamedDataSlot("myjg")End TryThread.SetData(jg, 100)TryFor mynum = 1 To 10Thread.SetData(jg, Thread.GetData(jg) - mynum)Console.WriteLine(data & " " & Now.ToLongTimeString & "=>" & (Thread.GetData(jg) + mynum) & "-" & mynum & ",計算結果為:" & Thread.GetData(jg))Thread.Sleep(randomGenerator.Next(10, 200))NextCatchConsole.WriteLine(data & " " & Now.ToLongTimeString & "線程異常終止!")'終止線程Thread.CurrentThread.Abort()End TryEnd Sub End Module為了查看效果,我特意用隨機數來代替固定的sleep時間,這樣更有說明力。
五、TLS小結
1)TLS基礎
可以使用托管線程本地存儲區 (TLS) 存儲某一線程和應用程序域所獨有的數據。 .NET Framework 提供了兩種使用托管 TLS 的方式:線程相關的靜態字段和數據槽。 線程相關的靜態字段提供的性能比數據槽的性能要好得多,而且它還啟用了編譯時類型檢查。
如果您可以在編譯時預料到您的確切需要,請使用線程相關的靜態字段(在 Visual Basic 中為線程相關的 Shared 字段)。 線程相關的靜態字段可提供最佳性能。 它們還具備編譯時類型檢查的優點。
如果只能在運行時發現您的實際需要,請使用數據槽。 數據槽比線程相關的靜態字段慢一些且更加難于使用,并且數據存儲為 Object 類型,因此必須將其強制轉換為正確的類型才能使用。
2)2種TLS特點
a)無論是使用線程相關的靜態字段還是使用數據槽,托管 TLS 中的數據都是線程和應用程序域組合所獨有的。
在應用程序域內部,一個線程不能修改另一個線程中的數據,即使這兩個線程使用同一個字段或槽時也不能。
當線程從多個應用程序域中訪問同一個字段或槽時,會在每個應用程序域中維護一個單獨的值。
例如,如果某個線程設置線程相關的靜態字段的值,接著它進入另一個應用程序域,然后檢索該字段的值,則在第二個應用程序域中檢索的值將不同于第一個應用程序域中的值。 在第二個應用程序域中為該字段設置一個新值不會影響第一個應用程序域中該字段的值。 同樣,當某個線程獲取兩個不同應用程序域中的同一命名數據槽時,第一個應用程序域中的數據將始終與第二個應用程序域中的數據無關。
b)如果您知道一些數據總是某個線程和應用程序域組合所獨有的,請向該靜態字段應用 ThreadStaticAttribute 特性。 與使用任何其他靜態字段一樣使用該字段。 該字段中的數據是每個使用它的線程所獨有的。線程相關的靜態字段的性能優于數據槽,并且具有編譯時類型檢查的優點。
c)請注意,任何類構造函數代碼都將在訪問該字段的第一個上下文中的第一個線程上運行。 在同一應用程序域內的所有其他線程或上下文中,如果字段是引用類型,它們將被初始化為 null(在 Visual Basic 中為 Nothing);如果字段是值類型,它們將被初始化為它們的默認值。 因此,您不應依賴于類構造函數來初始化線程相關的靜態字段。 而應避免初始化線程相關的靜態字段并假定它們初始化為 null (Nothing) 或它們的默認值。
d)在 .NET Framework 4 版中,可以使用 System.Threading.ThreadLocal(Of T) 類創建線程本地對象,在第一次使用該對象時它將惰式初始化,這樣就解決了c中所指問題。
d).NET Framework 提供了線程和應用程序域組合所獨有的動態數據槽。 數據槽包括兩種類型:命名槽和未命名槽。 兩者都是通過使用 LocalDataStoreSlot 結構來實現的。
若要創建命名數據槽,請使用 Thread.AllocateNamedDataSlot 或 Thread.GetNamedDataSlot 方法。 若要獲取對某個現有命名槽的引用,請將其名稱傳遞給 GetNamedDataSlot 方法。
若要創建未命名數據槽,請使用 Thread.AllocateDataSlot 方法。
e)對于命名槽和未命名槽,請使用 Thread.SetData 和 Thread.GetData 方法設置和檢索槽中的信息。 這些都是靜態方法,它們始終作用于當前正在執行它們的線程的數據。
f)命名槽可能很方便,因為您可以在需要它時通過將其名稱傳遞給 GetNamedDataSlot 方法來檢索該槽,而不是維護對未命名槽的引用。 但是,如果另一個組件使用相同的名稱來命名其線程相關的存儲區,并且有一個線程同時執行來自您的組件和該組件的代碼,則這兩個組件可能會破壞彼此的數據。 (本方案假定這兩個組件在同一應用程序域內運行,并且它們并不用于共享相同數據。)
g)線程使用本地存儲內存機制來存儲線程特定的數據。 公共語言運行時在創建每個進程時給它分配一個多槽數據存儲區數組。 線程可以分配數據存儲區中的數據槽,存儲和檢索槽中的數據值,以及在線程到期之后釋放槽以供重新使用。 每個線程的數據槽都是唯一的。 其他任何線程(即便是子線程)均無法獲取該數據。
1、用調試器調試線程
1)棧調用
以下面代碼為例
Imports System.ThreadingPublic Class Form1Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.ClickDim main_x As Integermain_x = 5Call sub1(main_x)End SubPrivate Sub sub1(sub1_x As Integer)Dim jg As Integerjg = sub1_x * sub1_xCall sub2(jg)End SubPrivate Sub sub2(sub2_x As Integer)Dim jg As Integerjg = sub2_x * 2'在下一句設置斷點jg = jg * jgEnd SubEnd Class此外,我們還可以研究一下這個線程的調用時堆棧情況,通過反匯編代碼,在調用堆棧窗口中選擇“轉到反匯編”
Private Sub sub2(sub2_x As Integer) 00000000 push ebp 00000001 mov ebp,esp 00000003 sub esp,14h 00000006 mov dword ptr [ebp-10h],ecx 00000009 mov dword ptr [ebp-4],edx 0000000c cmp dword ptr ds:[0256B1B8h],0 00000013 je 0000001A 00000015 call 62A16743 0000001a xor edx,edx 0000001c mov dword ptr [ebp-8],edx 0000001f mov eax,dword ptr [ebp-10h] 00000022 mov dword ptr [ebp-14h],eax 00000025 mov ecx,dword ptr [ebp-10h] 00000028 call 628F5C25 0000002d mov dword ptr [ebp-0Ch],eax 00000030 push 32h 00000032 mov edx,dword ptr [ebp-0Ch] 00000035 mov ecx,dword ptr [ebp-14h] 00000038 call FFDF30D0 0000003d mov ecx,dword ptr [ebp-4] 00000040 call FFFFFF70 00000045 mov ecx,63h 0000004a call FFDF2940 0000004f nop Dim jg As Integerjg = sub2_x * 2 00000050 mov eax,dword ptr [ebp-4] 00000053 mov edx,2 00000058 imul eax,eax,2 0000005b jno 00000062 0000005d call 62A19A30 00000062 mov dword ptr [ebp-8],eax '在下一句設置斷點jg = jg * jg 00000065 mov eax,dword ptr [ebp-8] 00000068 imul eax,dword ptr [ebp-8] 0000006c jno 00000073 0000006e call 62A19A30 00000073 mov dword ptr [ebp-8],eax End Sub 00000076 nop 00000077 nop 00000078 mov ecx,63h 0000007d call FFDF2A60 00000082 nop 00000083 mov esp,ebp 00000085 pop ebp 00000086 ret ESP是棧頂指針ebp是基址指針+-----++基址 ++-----++棧內容++棧內容++棧內容++棧內容++棧內容++棧頂 +
如上圖所示,基地的地址比棧頂的地址大,就是向下增長。
寄存器ebp和esp保存著當前的基址和棧頂地址
首先,進入函數時
00000000 push ebp
備份基址指針
00000001 mov ebp,esp
然后設置基址指針指向棧頂,相當于為當前棧清空了內容,做好在棧中分配局部變量的準備
00000003 sub esp,14h
完成棧(可以理解為本函數可訪問的棧)的空間分配,將棧頂指針向下增長14h(向下增長的意思是棧的空間增長規律是地址遞減)。相當于棧中已經容納了14h的空間
Dim jg As Integerjg = sub2_x * 2 00000050 mov eax,dword ptr [ebp-4] 00000053 mov edx,2 00000058 imul eax,eax,2 0000005b jno 00000062 0000005d call 617391D0 00000062 mov dword ptr [ebp-8],eax '在下一句設置斷點jg = jg * jg 00000065 mov eax,dword ptr [ebp-8] 00000068 imul eax,dword ptr [ebp-8] 0000006c jno 00000073 0000006e call 617391D0 00000073 mov dword ptr [ebp-8],eax從上面這段代碼可以看出來
sub2_x分配在了dword ptr [ebp-4] ,而jg分配在了dword ptr [ebp-8]
最后,離開函數時,恢復進入函數前棧的指針,相當于釋放了本次在棧中分配的空間
End Sub 00000083 mov esp,ebp 恢復棧頂指針00000085 pop ebp恢復基址指針00000086 ret2、修改默認棧的大小
Dim 線程變量名 As Thread = New Thread(函數名,以字節為單位的棧大小)比如
Dim mythread As Thread = New Thread(myfun,1024*512)分配了512kb字節
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的vb.net2019-多线程并行计算(3)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vb.net2019-多线程并行计算(2
- 下一篇: vb.net2019-多线程并行计算(4