• .NET中的线程本地存储(TLS)与AsyncLocal(一)


    一、TLS

          线程本地存储(Thread Local Storage),字面意思就是专属某个线程的存储空间。变量大体上分为全局变量局部变量,一个进程中的所有线程共享地址空间,这个地址空间被划分为几个固有的区域,比如堆栈区全局变量区等,全局变量存储在全局变量区,虚拟地址固定;局部变量存储在堆栈区,虚拟地址不固定。每个线程都有自己的栈空间,局部变量就存储在栈空间里面,虽然这个局部变量是与线程相联系的,但是这个局部变量不能在不同的函数栈中互相直接访问,但TLS可以,概括来讲,TLS是属于线程的“局部变量”,作用域为线程作用域,而不像全局变量为全局作用域,局部变量为局部作用域,因为这个变量独属于这个线程,所以这个变量是线程安全的。

    二、.NET中相关的类——ThreadLocal

          代码更直观,请看下面的代码:

     1 static void Main(string[] args)
     2 {
     3      ThreadLocal<int> threadLocal = new ThreadLocal<int>();
     4      //在主线程这个变量值为1
     5      threadLocal.Value = 1;
     6      new Thread(() => Console.WriteLine($"托管线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value++}")).Start();
     7      new Thread(() => Console.WriteLine($"托管线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value++}")).Start();
     8      new Thread(() => Console.WriteLine($"托管线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value++}")).Start();
     9      Console.WriteLine($"主线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value}");
    10 }



          输出结果如下:

    image

          可以看见每个这个变量的值对于每个线程来说都是独立的,一个线程对这个变量的修改只会影响本线程的读取,每个线程都有一份拷贝。

          有什么用呢?或者使用场景是什么呢?我觉得就是一句话——当每个线程都需要一个唯一的变量的时候

          比如早期版本的ASP.NET,每个线程处理一个Http请求,在处理这个Http请求的线程中,这个HttpContext在这个线程中是唯一的,所以在每个函数中都可以调用HttpContext.Current获得当前Http请求上下文对象,为了加深理解,请看下面的代码

     1 public class ConsoleContext
     2 
     3 {
     4 
     5      private static ThreadLocal<ConsoleContext> _tlsCCT = new ThreadLocal<ConsoleContext>();
     6 
     7      private string _consoleName;
     8 
     9     public string ConsoleName { get => _consoleName; }
    10 
    11      public static ConsoleContext Current { get => _tlsCCT.Value; }
    12 
    13     public ConsoleContext(string consoleName)
    14 
    15      {
    16 
    17          _consoleName = consoleName;
    18 
    19          _tlsCCT.Value = this;
    20 
    21      }
    22 
    23      public static void ResetContext() => _tlsCCT.Value = null;
    24 
    25 }
    26 
    27 public static void Excute()
    28 
    29 {
    30 
    31      Thread.Sleep(1000 * new Random(DateTime.Now.Millisecond).Next(5,10));
    32 
    33      Console.WriteLine("进入PrintName()");
    34 
    35      PrintName();
    36 
    37 }
    38 
    39 public static void PrintName()
    40 
    41 {
    42 
    43      var name = ConsoleContext.Current.ConsoleName;
    44 
    45      Console.WriteLine($"当前托管线程ID:{Thread.CurrentThread.ManagedThreadId} name:{name}");
    46 
    47 }
    48 
    49 static void Main(string[] args)
    50 
    51 {
    52 
    53      while (true)
    54 
    55      {
    56 
    57          var name = Console.ReadLine();
    58 
    59         ThreadPool.QueueUserWorkItem(state =>
    60 
    61          {
    62 
    63              Console.WriteLine($"当前托管线程ID:{Thread.CurrentThread.ManagedThreadId} name:{name}");
    64 
    65              new ConsoleContext(name);
    66 
    67              Excute();
    68 
    69              ConsoleContext.ResetContext();
    70 
    71          });
    72 
    73      }
    74 
    75 }


          简单来说,我模拟了一个Web服务器的行为,监听请求(在这里是监听键盘输入),若没有请求过来,服务器程序阻塞,若有请求过来(在这里是键盘输入),服务器响应请求,生成当前请求上下文,并生成一个TLS变量,然后执行Excute函数(相当HttpContext流入处理管道),最后清空TLS变量中的值,因为该线程是线程池中的线程,会被复用用于处理其他请求,不清空TLS会生成脏数据。

  • 相关阅读:
    与你一起学习MS Project基础篇:Project基础应用
    【项目管理工具】Microsoft Office Project 介绍
    学习Microsoft Visio(3)
    学习Microsoft Visio(2)
    学习Microsoft Visio(1)
    为什么管理人员都喜欢用Visio画图
    用Visio画流程图
    使用VISIO绘制组织结构图的操作方法
    C#(99):HttpClient网络HTTP请求和相应
    中国的名优绿茶
  • 原文地址:https://www.cnblogs.com/hkfyf/p/13209283.html
Copyright © 2020-2023  润新知