首页 > 编程语言 >C# 两大线程本地存储解决方案:ThreadStatic 与 ThreadLocal

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

时间:2025-01-10 20:44:56浏览次数:1  
标签:存储 Console C# ThreadLocal static 线程 logger

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:功能强大,适合更复杂的场景。

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

标签:存储,Console,C#,ThreadLocal,static,线程,logger
From: https://www.cnblogs.com/bcsg/p/18664673

相关文章

  • AT_abc248_h [ABC248Ex] Beautiful Subsequences 题解
    题目传送门前置知识树状数组|序列分治解法考虑序列分治,设因\(\max\)和\(\min\)形成的分节点先后为\(k_{1},k_{2}\)。对于\(j\in(mid,k_{1}]\),等价于统计满足\(\max\limits_{h=i}^{mid}\{a_{h}\}-\min\limits_{h=i}^{mid}\{a_{h}\}\lej-i+k\)的\(j\)的......
  • exGCD
    FileintGCD(inta,intb){ if(b==0)returna; returnGCD(b,a%b);}Question01[贝祖定理]根据ax+by=GCD(a,b)a,b均为正整数求x,y的整数值。Solution因为ax+by=GCD(a,b)并且GCD(a,b)=GCD(b,a%b)所以ax+by=GCD(b,a%b)由贝祖定理GCD(b,a%b)=bX+a%bY我们求出x,y和下......
  • C语言分支和循环(上)
    分⽀和循环分⽀和循环(上)1.if语句1.1if1.2else2.关系操作符3.条件操作符4.逻辑操作符:&&,||,!5.switch语句分⽀和循环(上)C语⾔是结构化的程序设计语⾔,这⾥的结构指的是顺序结构、选择结构、循环结构,C语⾔是能够实现这三种结构的,其实我们如果仔细分析,我们⽇......
  • 安装Fedora提示“Warning: /dev/root does not exist, could not boot”
    方法一:1.首先U盘启动,选择安装centos7,一直等,最终进入命令行:dracut:#dracut:#cd/devdracut:/dev#ls查看你的U盘,一般是第二块硬盘sdb4。如果还不确定,记下sdb3之后的几块盘,就拔出U盘,ls几次看看少了哪一个盘,一般就看不到sdb4了,如果看不到sdb4了,那我们的U盘就是sdb4,否则......
  • Product-Mechanics: 塑料:开模+挤塑量产
    1.“业务广告”+“金属模具样品”:2.生产设备:3.原料4.需求图:3D模型图:5.模具量产的成品......
  • 通过实现 HandlerMethodArgumentResolver 校验 Controller 参数
    HandlerMethodArgumentResolver接口是SpringMVC中的一个接口,用于解析控制器方法的参数。下面演示如何自定义一个@NotBlank注解,并实现相应的HandlerMethodArgumentResolver,来校验前端传递的参数是否为空。实际项目中已经有现成的注解可用了,这里仅用来演示HandlerMethodArgument......
  • GitLab CISO谈DevSecOps成功:主动监测与指标是关键
    原创MirkoZorz信息安全D1netGitLab的CISOJoshLemos探讨了从DevOps到DevSecOps的转变,指出构建系统复杂性和安全工具集成是企业面临的主要挑战,他建议简化构建系统,将安全检查直接融入流水线,并采取措施避免次优设计决策,同时,强调了以软件最小化为目标、在非阻塞模式下逐个项目......
  • cursor创建微信小程序+云函数+数据库
    111111111111111111111111111111111111111111   我想要创建一个微信小程序,请帮我创建完整的微信小程序的目录和所有必需的文件,保证其可以运行。  我想要做一个基于音标来背单词的小程序,首页有三个功能,第一个功能是背单词,第二个功能是,查看我背过的单词,第三个......
  • 【嵌入式编程】 C 程序代码如何实现高内聚低耦合
    一、原理篇低耦合,是指模块之间尽可能的使其独立存在,模块之间不产生联系不可能,但模块与模块之间的接口应该尽量少而简单。这样,高内聚从整个程序中每一个模块的内部特征角度,低耦合从程序中各个模块之间的关联关系角度,对我们的设计提出了要求。程序设计和软件工程发展过程中产生的......
  • netplan apply报错No module named ‘netifaces‘
    Ubuntu20.04.5LTS\n\l,ctrl+alt+f2切换字符登录f1切换图形处理办法:root登录执行root@node37:/disk1/Qwen2.5-72B-Instruct-GPTQ-Int4#cat/etc/netplan/01-network-manager-all.yaml #LetNetworkManagermanagealldevicesonthissystemnetwork: version:2......