用托管代码实现线程本地存储的三种方法
王曼韬
四川农业大学,(625000),wmt1708@263
摘要:多线程环境下实现线程本地存储是多线程应用中的一种基本技术,本文详细论述了用托管代码实现线程本地存储的三种方法,并对这些方法进行比较。给出如何使用这三种方法的建议。
关键字:TLS  多线程托管代码
1、引言
在多线程应用中,常常出现需要将数据与对象的实例联系起来的时候,可以使用线程本地存储(Thread Local Storage, TLS)将数据与执行的特定线程联系起来。比如,我们可以将线程的某个时间与线程联系起来,然后当线程终止运行时,就能够确定线程的寿命。再比如,C中的strtok()函数,该函数的作用是使用用户要求的记号来分离字符串,我们希望使用多个线程调用strtok方法来对多个串进行分离。那么实现TLS的原因是什么呢?如第二个例子中的strtok()函数在开始调用的时候需要给出两个参数,记号串和源串的开始地址,然后对串进行分离;下面程序片断说明了该函数的用法,在例子中使用“,”,空格符和换行符作分隔符。
char string[] = "A string\tof ,,tokens\nand some ";
char seps[] = " ,\t\n";
char *token;
……
token = strtok( string, seps );writeline输出数值变量
while( token != NULL )
{  printf( " %s  ", token );
token = strtok( NULL, seps );  }
输出为:A  string  of  tokens  and  some
该程序在单线程环境下没有任何问题。但是由于C/C++运行期库是在多线程应用程序出现前的许多年设计的,因此在多线程环境下,如果需希望使用多个线程调用strtok方法来对多个串进行分离时,不会得到预期的结果,因为所有线程的工作都在最后一个串,发生这样的结果是因为静态变量的共享造成
的。此时,让这个变量成为TLS就可以解决这个问题。下面具体说明三种实现TLS的方法,例子用托管代码VB.NET实现。
2、实现线程本地存储的三种方法
2.1使用未命名的数据槽实现TLS
使用Thread类中的AllocateDataSlot()方法,它的功能是在所有线程上分配数据槽,或者说为每个线程分配数据槽。为每个线程分配数据槽后,可以使用线程类中的Thread.SetData() 和Thread.GetData()方法来设置和获取数据。特点是,线程使用本地存储内存机制来存储线程特定的数据,公共语言运行库在创建每个进程时给它分配一个多槽数据存
- 1 -
储区数组。线程可以在数据存储区中分配数据槽,在槽中存储和检索数据值,以及在线程过期后释放槽供重用。数据槽对于每个线程是唯一的。其他任何线程(即使是子线程)都无法获取这些数据。
'此例说明未命名数据槽用法
Imports System.Threading
Imports System.Threading.Thread
Module Module1
Sub Main()
Dim i As Integer
Dim myThread(3) As Thread '三个线程
For i = 0 To 2
myThread(i) = New Thread(AddressOf ThreadExample.ThreadProc)
myThread(i).Start()
Next
For i = 0 To 2
myThread(i).Join()
Next
Console.WriteLine("over!")
End Sub
Public Class ThreadExample
'数据槽mySlot为共享数据槽
Shared mySlot As LocalDataStoreSlot
'randomGenerator产生一个随机数
Shared randomGenerator As Random = New Random
Shared Sub New()
'分配未命名数据槽
mySlot = Thread.AllocateDataSlot()
End Sub
Shared Sub ThreadProc()
'为未命名数据槽填充数据
Thread.SetData(mySlot, randomGenerator.Next(1, 200))
'输出线程id和该线程数据槽中的数据
Console.WriteLine("Data in Thread {0} Data slot :{1} . ",
AppDomain.GetCurrentThreadId, Thread.GetData(mySlot))
Thread.Sleep(3000)
'输出线程id和该线程数据槽中的数据
Console.WriteLine("Data in Thread {0} Data slot :{1} . ",
- 2 -
AppDomain.GetCurrentThreadId, Thread.GetData(mySlot))
Thread.Sleep(3000)
End Sub
End Class
End Module
某次的输出结果为:
2.2使用命名数据槽实现TLS
使用Thread类中的AllocateNamedDataSlot(“数据槽名称”)方法,它的功能是线程使用本地存储内存机制来存储线程特定的数据。为每个线程分配数据槽后,可使用线程类中的Thread.SetData() 和Threa
d.GetData()方法来设置和获取数据。特点是,公共语言运行库在创建每个进程时给它分配一个多槽数据存储区数组。线程可以在数据存储区中分配数据槽,在槽中存储和检索数据值,以及在线程过期后释放槽供重用。数据槽对于每个线程是唯一的。其他任何线程(即使是子线程)都无法获取这些数据。使用此方法分配的数据槽必须使用FreeNamedDataSlot来释放。
'此例说明命名数据槽用法
Imports System.Threading
Imports System.Threading.Thread
Module Module1
'Dim k As Integer = 0
Sub Main()
Dim i As Integer
'三个线程
Dim myThread(3) As Thread
For i = 0 To 2
myThread(i) = New Thread(AddressOf ThreadExample.ThreadProc)
myThread(i).Start()
Next
Console.ReadLine()
End Sub
Public Class ThreadExample
'数据槽mySlot为共享数据槽
Shared mySlot As LocalDataStoreSlot
- 3 -
'randomGenerator产生一个随机数
Shared randomGenerator As Random = New Random
Shared Sub New()
'分配命名数据槽
mySlot = Thread.AllocateNamedDataSlot("theNameofSlot")
End Sub
Shared Sub ThreadProc()
'为未命名数据槽填充数据
Thread.SetData(mySlot, randomGenerator.Next(1, 200))
'输出线程Id和该线程数据槽中的数据
Console.WriteLine("Data in Thread {0} Data slot :{1} ", AppDomain.GetCurrentThreadId, Thread.GetData(mySlot))
Thread.Sleep(2000)
Console.WriteLine("Data in Thread {0} Data slot :{1} ", AppDomain.GetCurrentThreadId, Thread.GetData(mySlot))
Thread.Sleep(2000)
End Sub
End Class
End Module
某次的输出结果为:
2.3使用关键字<ThreadStatic()>实现TLS
在一个静态变量前面加上关键字<ThreadStatic()>指示静态字段的值对于每个线程都
是唯一的(在C#中用[ThreadStaticAttribute])。
'采用ThreadStaticAttribute实现TLS
Imports System.Threading
Imports System.Threading.Thread
Module Module1
Sub Main()
Dim myThread1, myThread2 As Thread
myThread1 = New Thread(AddressOf ThreadExample.ThreadProc)
myThread2 = New Thread(AddressOf ThreadExample.ThreadProc)
myThread1.Start()
-
4 -
myThread2.Start()
Console.ReadLine()
End Sub
Public Class ThreadExample
'指示字段i的值对于每个线程都是唯一的。
<ThreadStatic()> _
Shared i As Integer
Public Shared Sub ThreadProc()
i = 0
'Thread.VolatileWrite(i, 0)
While (i < 100)
Console.Write("{0} ,", i)
i = i + 1
End While
Console.WriteLine("**\n**")
End Sub
End Class
End Module
某次的输出结果为:
结果分析:输出0,1,……,99的数字两次(如果不用TLS,那么输出结果将是输出0,1,……,99一次,下面是不才用TLS时的结果)
结果分析:总体上看输出0到99一次,但是输出0两次,原因是在执行第一次i+1之前,两个线程都运行了输出语句。
- 5 -
3、结论
在上面三种方法中,第三种方法是最简单的一种,只需要在类变量的前面申明即可;第二种方法比第一种方法简单,因为不必在不同的线程间共享一个通用的数据槽,但是可能和别的代码的相同命名的槽具有相同的名称导致冲突,因此使用时必须小心不能有同名的数据槽。总体上来说,如果只对单个的类使用TLS,那么使用第三种方法较好,如果需要在多个地方使用TLS(多个类)那么一般使用基于未命名数据槽或命名数据槽的方法。如果需要在调用路径上每次都需要TLS,还可以考虑调用上下文
机制。最后,还有一种可以替代TLS的简单的方案,就是每个线程用一个对象实例来代替对TLS的需要。
参考文献:
[1]Jeffrey Richter 著, Programming Applications for Microsoft Windows,2000
[2]Bill Evjen/ Billy Hollis著,杨浩译, VB.NET高级编程(第3版) ,2005
Three method of using TLS with managed code
Wang mantao
Sichuan Agricultural University,(625000),wmt1708@263
Abstract: The TLS is one kind of fundamental technique in the in multithreaded applications environment, and the original was detailedly expounded three kinds of means how to use TLS with managed code,and carries on the comparing to these meanss . Finally , give out how to employ this three kinds of proposals of means.
Keywords: TLS ; multithread; Managed code
- 6 -

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。