數學之路-vb.net並行計算(3),-vb.net並行計算
接上節,我們可以使用下面語句建立一個執行緒區域變數,利用靜態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 Thread
Dim mythread2 As Thread
Dim 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 LocalDataStoreSlot
jg = Thread.GetNamedDataSlot(data)
Thread.SetData(jg, 100)
Try
For mynum = 1 To 10
Thread.SetData(jg, Thread.GetData(jg) - mynum)
Console.WriteLine(data & " " & Now.ToLongTimeString & "=>" & (Thread.GetData(jg) + mynum) & "-" & mynum & ",計算結果為:" & Thread.GetData(jg))
Thread.Sleep(2)
Next
Catch
Console.WriteLine(data & " " & Now.ToLongTimeString & "線程異常終止!")
'終止線程
Thread.CurrentThread.Abort()
Finally
Thread.FreeNamedDataSlot(data)
End Try
End Sub
End Module
運行結果如我們所願,jg變數通過動態TLS提供的槽機制實現了執行緒區域變數
'分配一個新的槽,這個槽存放執行緒區域資料,槽名稱為
'myjg,名稱必須唯一
Thread.AllocateNamedDataSlot(data)
Dim jg As LocalDataStoreSlot
jg = Thread.GetNamedDataSlot(data)
Thread.SetData(jg, 100)
以上代碼是關健,我們使用命名資料槽,當然,我們也可以使用未命名槽,因為未命名資料槽相對較簡單,所以這裡使用了命名資料槽,向大家示範一下其功能。
注意:
如果使用 AllocateNamedDataSlot 方法已指派已經存在的指定名稱的槽,此方法會引發異常,且無法測試是否已指派某個槽。另外,使用此方法分配的資料槽必須使用 FreeNamedDataSlot 來釋放。
本部落格所有內容是原創,如果轉載請註明來源http://blog.csdn.net/myhaspl/
本例中,我們分配槽使用下面語句
Thread.AllocateNamedDataSlot(data)
擷取某個命名槽的引用,以便進行下一步操作
jg = Thread.GetNamedDataSlot(data)
Thread.SetData和Thread.GetData可寫、讀槽中資料
四、資料槽的值線上程或內容物件之間不共用
LocalDataStoreSlot 結構可用作本機存放區記憶體機制,線程和上下文可以使用此機制分別儲存線程特定的資料和上下文特定的資料。 通用語言執行平台在建立每個進程時給它分配一個多槽資料存放區區數組。 線程或上下文調用各種函數在資料存放區區中分配資料槽、在槽記憶體儲和檢索資料值、以及釋放資料槽以便線上程或上下文到期後重新使用它。
對於每個線程或上下文,資料槽都是唯一的;它們的值線上程或內容物件之間不共用。 資料槽可根據名稱或根據索引號來分配。
我們可以從下面程式看出
Imports System
Imports System.Threading
Module Module1
Sub Main()
Dim mythread1 As Thread
Dim mythread2 As Thread
Dim mythread3 As Thread
Dim 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 Sub
Public Sub mythreadrun(ByVal data As Object)
Dim randomGenerator As New Random()
Dim mynum As Integer
'分配一個新的槽,這個槽存放執行緒區域資料,槽名稱為
'myjg
Dim jg As LocalDataStoreSlot
Try
jg = Thread.AllocateNamedDataSlot("myjg")
Catch
jg = Thread.GetNamedDataSlot("myjg")
End Try
Thread.SetData(jg, 100)
Try
For mynum = 1 To 10
Thread.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))
Next
Catch
Console.WriteLine(data & " " & Now.ToLongTimeString & "線程異常終止!")
'終止線程
Thread.CurrentThread.Abort()
End Try
End Sub
End Module
本部落格所有內容是原創,如果轉載請註明來源http://blog.csdn.net/myhaspl/
為了查看效果,我特意用隨機數來代替固定的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.Threading
Public Class Form1
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim main_x As Integer
main_x = 5
Call sub1(main_x)
End Sub
Private Sub sub1(sub1_x As Integer)
Dim jg As Integer
jg = sub1_x * sub1_x
Call sub2(jg)
End Sub
Private Sub sub2(sub2_x As Integer)
Dim jg As Integer
jg = sub2_x * 2
'在下一句設定斷點
jg = jg * jg
End Sub
End 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 Integer
jg = 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 Integer
jg = 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 ret
2、修改預設棧的大小
Dim 線程變數名 As Thread = New Thread(函數名,以位元組為單位的棧大小)
比如
Dim mythread As Thread = New Thread(myfun,1024*512)
分配了512kb位元組