C# 两大线程本地存储解决方案:ThreadStatic 与 ThreadLocal

C# 两大线程本地存储解决方案:ThreadStatic 与 ThreadLocal

一、线程本地存储

在 C# 中,static 关键字定义的变量,其作用域是在应用程序域(AppDomain)内共享的。因此,在多线程操作时,对同一个静态变量进行操作可能会导致并发问题,如锁竞争等。这种情况下,我们需要一种机制,使某些变量对每个线程独立,从而避免锁竞争问题。

线程本地存储(Thread-Local Storage,TLS)是一种机制,允许每个线程拥有自己的变量副本。这意味着每个线程对变量的访问都是独立的,互不干扰。

例如,线程安全的 ConcurrentBag 底层的实现就用到了 ThreadLocal 变量,来实现线程间的独立数据存储。

二、C# 中的解决方案

1. ThreadStatic 特性

概念与用法

ThreadStatic 是一个用于静态字段的特性,可以使字段在每个线程中都有一个独立的实例。

示例代码

internal class Program
{
    [ThreadStatic]
    private static int _threadSpecificData = -1;

    static void Main(string[] args)
    {
        Task.Run(() =>
        {
            _threadSpecificData = 1;
            Console.WriteLine($"第1个线程,线程id: {Environment.CurrentManagedThreadId}: {_threadSpecificData}");
        });

        Task.Run(() =>
        {
            _threadSpecificData = 2;
            Console.WriteLine($"第2个线程,线程id:{Environment.CurrentManagedThreadId}: {_threadSpecificData}");
        });

        Console.ReadKey();
    }
}

输出:

第1个线程,线程id: 9: 1
第2个线程,线程id:8: 2

注意事项

只能用于 static 字段。
每个线程使用变量前,初始化变量。定义变量时,赋值的变量初始值,只会有一个线程的变量有这个初始值,其他线程变量副本是没有赋值的状态。特别主要引用类型的变量,会是null.

2. ThreadLocal 类

概念与用法

ThreadLocal<T> 是 .NET 提供的一个泛型类,用于更灵活地实现线程本地存储。它允许每个线程拥有独立的值,并支持通过工厂方法初始化每个线程的值。

示例代码

 internal class Program
 {
     static void Main(string[] args)
     {
         ThreadLocal<int> _threadSpecificData = new ThreadLocal<int>(() => Thread.CurrentThread.ManagedThreadId);

         Task.Run(() =>
         {
             Console.WriteLine($"第1个线程,线程id: {Environment.CurrentManagedThreadId}: value: {_threadSpecificData}");
         });

         Task.Run(() =>
         {
             Console.WriteLine($"第1个线程,线程id: {Environment.CurrentManagedThreadId}: value: {_threadSpecificData}");
         });
         Console.ReadKey();
     }
 }

输出:

第1个线程,线程id: 8: value: 8
第1个线程,线程id: 6: value: 6

特点

支持 Func 委托延迟初始化

区别

特性 ThreadStatic ThreadLocal
使用范围 仅用于 static 字段 不限制
初始化方式 无法直接初始化,需显式赋值 支持延迟初始化
管理灵活性 简单,但功能受限 功能强大,适合复杂场景

三、线程本地存储到底存储在什么地方

线程本地存储的引用地址是存在 TLS(Thread Local Storage)上。
TLS(Thread Local Storage): 用于存储线程私有数据。
TEB(Thread Environment Block): 在线程的本地存储之上,是线程的环境块,保存了线程的上下文信息。
PEB(Process Environment Block): 进程环境块,用于存储进程级的全局信息。

以下是内存结构的示意图:

+----------------+
|   PEB          |
+----------------+
|   TEB          | <-- 多个线程
+----------------+
|   TLS          |
+----------------+

四、应用场景

示例 1:用于日志记录中的线程上下文

在日志记录中,通过线程本地存储可以为每个线程存储独立的上下文信息。在多线程应用程序中,每个线程可能需要记录不同级别的日志。使用ThreadLocal可以为每个线程分配一个独立的日志记录器实例,从而避免日志记录的竞争和同步问题。

internal class Program
{
    static void Main(string[] args)
    {
        Task.Run(() =>
        {
            logger.SetContext("线程A");                
            logger.log("在执行第1步操作");
            Thread.Sleep(200);
            logger.log("在执行第2步操作");
            logger.log("执行完了");
        });

        Task.Run(() =>
        {
            logger.SetContext("线程B");
            logger.log("在执行第1步操作");
            logger.log("遇到异常,退出了");
        });
        Console.ReadKey();
    }
}

class logger
{
    private static ThreadLocal<string> _context = new ThreadLocal<string>();

    public static void SetContext(string context)
    {
        _context.Value = context;
    }

    public static void log(string message)
    {
        Console.WriteLine($"[{_context.Value}]{message}");
    }
}

输出:

[线程A]在执行第1步操作
[线程B]在执行第1步操作
[线程B]遇到异常,退出了
[线程A]在执行第2步操作
[线程A]执行完了

五、总结

C# 提供了两种线程本地存储解决方案:
ThreadStatic:简单直接,但初始化灵活性较差。
ThreadLocal:功能强大,适合更复杂的场景。

根据具体应用场景选择合适的方案,可以显著提高程序的性能和线程安全性。

posted @   dotNet编程拾光  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤
点击右上角即可分享
微信分享提示