线程和进程最大的一个区别就在于线程间可以共享数据和资源,而进程则充分地隔离。在很多场合,即使同一进程的多个线程之间拥有相同的内存空间,也需要在逻辑上为某些线程分配独享的数据。例如,在实际开发中往往会针对一些ORM如EF一类的上下文实体做线程内唯一实例的设置,这时就需要用到下面提到的技术。
(1)线程本地存储(Thread Local Storage,TLS)
很多时候,程序员可能会希望拥有线程内可见的变量,而不希望其他线程对其进行访问和修改(传统方式中的静态变量是对整个应用程序域可见的),这就需要用到TLS的概念。所谓的线程本地存储(TLS)是指存储在线程环境块内的一个结构,用来存放该线程内独享的数据。进程内的线程不能访问不属于自己的TLS,这就保证了TLS内的数据在线程内是全局共享的,而对于线程外确实不可见的。
(2)定义和使用TLS变量
在.NET中提供了下列连个方法来存取线程独享的数据,它们都定义在System.Threading.Thread类型中:
① object GetData(LocalDataStoreSlot slot)
② void SetData(LocalDataStoreSlot slot, object data)
下面的代码示例则展示了这个机制的使用方法:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("开始测试数据插槽:");
// 创建五个线程来同时运行,但是这里不适合用线程池,
// 因为线程池内的线程会被反复使用导致线程ID一致
for (int i = 0; i < 5; i++)
{
Thread thread = new Thread(ThreadDataSlot.Work);
thread.Start();
}
Console.ReadKey();
}
}
///
/// 包含线程方法和数据插槽
///
public class ThreadDataSlot
{
// 分配一个数据插槽,注意插槽本身是全局可见的,因为这里的分配是在所有线程
// 的TLS内创建数据块
private static LocalDataStoreSlot localSlot = Thread.AllocateDataSlot();
// 线程要执行的方法,操作数据插槽来存放数据
public static void Work()
{
// 将线程ID注册到数据插槽中,一个应用程序内线程ID不会重复
Thread.SetData(localSlot, Thread.CurrentThread.ManagedThreadId);
// 查看一下刚刚插入的数据
Console.WriteLine("线程{0}内的数据是:{1}",Thread.CurrentThread.ManagedThreadId.ToString(),Thread.GetData(localSlot).ToString());
// 这里线程休眠1秒
Thread.Sleep(1000);
// 查看其他线程的运行是否干扰了当前线程数据插槽内的数据
Console.WriteLine("线程{0}内的数据是:{1}", Thread.CurrentThread.ManagedThreadId.ToString(), Thread.GetData(localSlot).ToString());
}
}

(3)ThreadStaticAttribute特性的使用
除了使用上面说到的数据槽之外,我们还有另一种方式,即ThreadStaticAttribute特性。申明了该特性的变量,会被.NET作为线程独享的数据来使用。我们可以将其理解为一种被.NET封装了的TLS机制,本质上,它仍然使用了线程环境块来存放数据。
下面的示例代码展示了ThreadStaticAttribute特性的使用:
class Program
{
static void Main(string[] args)
{
Console.WriteLine(“开始测试数据插槽:”);
// 创建五个线程来同时运行,但是这里不适合用线程池,
// 因为线程池内的线程会被反复使用导致线程ID一致
for (int i = 0; i < 5; i++)
{
Thread thread = new Thread(ThreadStatic.Work);
thread.Start();
}
Console.ReadKey();
}
}
///
/// 包含线程静态数据
///
public class ThreadStatic
{
// 值类型的线程静态数据
[ThreadStatic]
private static int threadId = 0;
// 引用类型的线程静态数据
private static Ref refThreadId = new Ref();
///
/// 线程执行的方法,操作线程静态数据
///
public static void Work()
{
// 存储线程ID,一个应用程序域内线程ID不会重复
threadId = Thread.CurrentThread.ManagedThreadId;
refThreadId.Id = Thread.CurrentThread.ManagedThreadId;
// 查看一下刚刚插入的数据
Console.WriteLine("[线程{0}]:线程静态值变量:{1},线程静态引用变量:{2}", Thread.CurrentThread.ManagedThreadId.ToString(), threadId, refThreadId.Id.ToString());
// 睡眠1s
Thread.Sleep(1000);
// 查看其他线程的运行是否干扰了当前线程静态数据
Console.WriteLine("[线程{0}]:线程静态值变量:{1},线程静态引用变量:{2}", Thread.CurrentThread.ManagedThreadId.ToString(), threadId, refThreadId.Id.ToString());
}
}
///
/// 简单引用类型
///
public class Ref
{
private int id;
public int Id
{
get
{
return id;
}
set
{
id = value;
}
}
}
