Skip to content

线程本地存储(Thread Local Storage, TLS)(1)线程数据插槽

🏷️ C# 学习

所谓的线程本地存储(TLS),是指存储在线程环境块内的一个结构,用了存放该线程内独享的数据。

进程内的线程不能访问不属于自己的 TLS,这就保证了 TLS 内的数据在线程内是全局共享的,而对线程外却是不可见的。

.NET 提供了简单的机制供程序员使用本地存储,定义在 System.Threading.Thread 类型内的 AllocateDataSlotAllocateNamedDataSlot 方法提供一个存储在所有线程内的数据插槽,而通过这个插槽结构,程序员可以使用下列两个方法来存取线程独享的数据,这两个方法都定义在 System.Threading.Thread 类型中。

cs
//
// 摘要:
//     在当前线程的当前域中从当前线程上指定的槽中检索值。为了获得更好的性能,请改用以 System.ThreadStaticAttribute 特性标记的字段。
//
// 参数:
//   slot:
//     要从其中获取值的 System.LocalDataStoreSlot。
//
// 返回结果:
//     检索到的值。
public static object GetData(LocalDataStoreSlot slot);

//
// 摘要:
//     在当前正在运行的线程上为此线程的当前域在指定槽中设置数据。为了获得更好的性能,请改用以 System.ThreadStaticAttribute 特性标记的字段。
//
// 参数:
//   slot:
//     在其中设置值的 System.LocalDataStoreSlot。
//
//   data:
//     要设置的值。
public static void SetData(LocalDataStoreSlot slot, object data);

示例代码

ThreadDataSlot.cs

cs
using System;
using System.Threading;

namespace ThreadDataSlot
{
    class ThreadDataSlot
    {
        /// <summary>
        /// 分配一个数据插槽,注意插槽本身是全局可见的
        /// 因为这里的分配是在所有线程的 TLS 内建立数据块
        /// </summary>
        static LocalDataStoreSlot _localSlot = Thread.AllocateDataSlot();
        /// <summary>
        /// 线程方法,操作数据插槽来存放数据
        /// </summary>
        public static void DoWork()
        {
            // 这里把线程 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());
        }
    }
}

Program.cs

cs
using System;
using System.Threading;

namespace ThreadDataSlot
{
    class Program
    {
        static void Main(string args)
        {
            Console.WriteLine("现在开始测试数据插槽");
            // 开辟 5 个线程来同时运行
            // 这里不适合用线程池,
            // 因为线程池内的线程会被反复使用,
            // 导致线程 ID 一致
            for (int i = 0; i < 5; i++)
            {
                Thread thread = new Thread(ThreadDataSlot.DoWork);
                thread.Start();
            }
            Console.Read();
        }
    }
}