>>返回《C# 并发编程》

1. 简介

  • 普通共享变量:

    • 在某个类上用静态属性的方式即可。
  • 多线程共享变量
    • 希望能将这个变量的共享范围缩小到单个线程
    • 无关系的B线程无法访问到A线程的值;

[ThreadStatic]特性、ThreadLocal<T>CallContextAsyncLocal<T> 都具备这个特性。

例子:

由于 .NET Core 不再实现 CallContext,所以下列代码只能在 .NET Framework 中执行

class Program
{
//对照
private static string _normalStatic; [ThreadStatic]
private static string _threadStatic;
private static ThreadLocal<string> _threadLocal = new ThreadLocal<string>();
private static AsyncLocal<string> _asyncLocal = new AsyncLocal<string>();
static void Main(string[] args)
{
Parallel.For(0, 4, _ =>
{
var threadId = Thread.CurrentThread.ManagedThreadId; var value = $"这是来自线程{threadId}的数据"; _normalStatic = value;
_threadStatic = value;
CallContext.SetData("value", value);
_threadLocal.Value = value;
_asyncLocal.Value = value; Console.WriteLine($"Use Normal; Thread:{threadId}; Value:{_normalStatic}");
Console.WriteLine($"Use ThreadStaticAttribute; Thread:{threadId}; Value:{_threadStatic}");
Console.WriteLine($"Use CallContext; Thread:{threadId}; Value:{CallContext.GetData("value")}");
Console.WriteLine($"Use ThreadLocal; Thread:{threadId}; Value:{_threadLocal.Value}");
Console.WriteLine($"Use AsyncLocal; Thread:{threadId}; Value:{_asyncLocal.Value}");
}); Console.Read();
}
}

输出:

Use Normal;                Thread:15; Value:10
Use [ThreadStatic]; Thread:15; Value:15
Use Normal; Thread:10; Value:10
Use Normal; Thread:8; Value:10
Use [ThreadStatic]; Thread:8; Value:8
Use CallContext; Thread:8; Value:8
Use [ThreadStatic]; Thread:10; Value:10
Use CallContext; Thread:10; Value:10
Use CallContext; Thread:15; Value:15
Use ThreadLocal; Thread:15; Value:15
Use ThreadLocal; Thread:8; Value:8
Use AsyncLocal; Thread:8; Value:8
Use ThreadLocal; Thread:10; Value:10
Use AsyncLocal; Thread:10; Value:10
Use AsyncLocal; Thread:15; Value:15

结论:

  • Normal 为对照组
  • Nomal 的 Thread 与 Value 值不同,因为读到了其他线程修改的值
  • 其他的类型,存储的值,在 Parallel 启动的线程间是隔离的

2. 异步下的共享变量

日常开发过程中,我们经常遇到异步的场景。

异步可能会导致代码执行线程的切换。

例如:

测试:[ThreadStatic]特性、ThreadLocal<T>AsyncLocal<T> ,三种共享变量被异步代码赋值后的表现。

class Program
{
[ThreadStatic]
private static string _threadStatic;
private static ThreadLocal<string> _threadLocal = new ThreadLocal<string>();
private static AsyncLocal<string> _asyncLocal = new AsyncLocal<string>();
static void Main(string[] args)
{
_threadStatic = "set";
_threadLocal.Value = "set";
_asyncLocal.Value = "set";
PrintValuesInAnotherThread();
Console.ReadKey();
} private static void PrintValuesInAnotherThread()
{
Task.Run(() =>
{
Console.WriteLine($"ThreadStatic: {_threadStatic}");
Console.WriteLine($"ThreadLocal: {_threadLocal.Value}");
Console.WriteLine($"AsyncLocal: {_asyncLocal.Value}");
});
}
}

输出:

ThreadStatic:
ThreadLocal:
AsyncLocal: set

结论:

在异步发生后,线程被切换,只有 AsyncLocal 还能够保留原来的值.

  • CallContext 也可以实现这个需求,但 .Net Core 没有被实现,这里就不过多说明。

我们总结一下这些变量的表现:

实现方式 DotNetFx DotNetCore 是否支持数据向辅助线程的
[ThreadStatic]
ThreadLocal
CallContext.SetData(string name, object data) 仅当参数 data 对应的类型实现了 ILogicalThreadAffinative 接口时支持
CallContext.LogicalSetData(string name, object data)
AsyncLocal

辅助线程: 用于处理后台任务,用户不必等待就可以继续使用应用程序,比如线程池线程。

注意:

  • [ThreadStatic]特性、ThreadLocal<T> 最好不要用在线程池线程

    • 线程池线程是可重用的,线程不会销毁,当线程被重用时,之前使用保存的值依然存在,可能造成影响
  • 使用 AsyncLocal<T> 可以用在线程池线程
    • 线程使用后回归线程池, AsyncLocal<T> 的状态会被清除,无法访问之前的值
  • new Task(...) 默认不是新建一个线程,而是使用线程池线程

3. 解析 AsyncLocal

  • AsyncLocal<T>Value 属性的真正的数据存取是通过 ExecutionContextinternal 的方法 GetLocalValueSetLocalValue 将数据存到 当前ExecutionContext 上的 m_localValues 字段上

    • ExecutionContext 会根据执行环境进行流动,详见 《ExecutionContext(执行上下文)综述》
    • 简单描述就是,线程发生切换的时候, ExecutionContext 会在前一个线程中被捕获,流向下一个线程,它所保存的数据也就随之流动了
      • 在所有会发生线程切换的地方,基础类库(BCL) 都为我们封装好了对 ExecutionContext 的捕获
      • 例如:
        • new Thread(...).Start()
        • new Task(...).Start()
        • Task.Run(...)
        • ThreadPool.QueueUserWorkItem(...)
        • await 语法糖
  • m_localValues 类型是 IAsyncLocalValueMap

3.1. IAsyncLocalValueMap 的实现

以下为基础设施提供的实现:

类型 元素个数
EmptyAsyncLocalValueMap 0
OneElementAsyncLocalValueMap 1
TwoElementAsyncLocalValueMap 2
ThreeElementAsyncLocalValueMap 3
MultiElementAsyncLocalValueMap 4 ~ 16
ManyElementAsyncLocalValueMap > 16

随着 ExecutionContext 所关联的 AsyncLocal 数量的增加, IAsyncLocalValueMap实现将会在 ExecutionContextSetLocalValue 方法中被不断替换

  • 查询的时间复杂度和空间复杂度依次递增

3.2. 结论

  • AsyncLocal 类型存储数据,是在自己线程的 ExecutionContext
  • ExecutionContext 的实例会随着异步或者多线程的启动而被流向执行后续代码的其他线程,保证了启动异步的线程存储的数据可以被访问到
  • 数据存到 IAsyncLocalValueMap 类型的变量中,此变量会根据存储的 AsyncLocal 变量个数而切换实现
    • 支持存储量越大的实现类型,性能越

参考资料

《浅析 .NET 中 AsyncLocal 的实现原理》 --- 黑洞视界

多线程共享变量和 AsyncLocal的更多相关文章

  1. 『Python』 多线程 共享变量的实现

    简介: 对于Python2而言,对于一个全局变量,你的函数里如果只使用到了它的值,而没有对其赋值(指a = XXX这种写法)的话,就不需要声明global. 相反,如果你对其赋了值的话,那么你就需要声 ...

  2. Java多线程共享变量控制

    1. 可见性 如果一个线程对共享变量值的修改,能够及时的被其他线程看到,叫做共享变量的可见性.如果一个变量同时在多个线程的工作内存中存在副本,那么这个变量就叫共享变量 2. JMM(java内存模型) ...

  3. EntityFrameworkCore之工作单元的封装

    1. 简介 2. DbContext 生命周期和使用规范 2.1. 生命周期 2.2. 使用规范 2.3. 避免 DbContext 线程处理问题 3. 封装-工作单元 3.1. 分析 3.2. 设计 ...

  4. 谈谈.NET Core下如何利用 AsyncLocal 实现共享变量

    前言 在Web 应用程序中,我们经常会遇到这样的场景,如用户信息,租户信息本次的请求过程中都是固定的,我们希望是这种信息在本次请求内,一次赋值,到处使用.本文就来探讨一下,如何在.NET Core 下 ...

  5. 【.NET深呼吸】基于异步上下文的本地变量(AsyncLocal)

    在开始吹牛之前,老周说两个故事. 第一个故事是关于最近某些别有用心的人攻击.net的事,其实我们不用管它们,只要咱们知道自己是.net爱好者就行了,咱们就是因为热爱.net才会选择它.这些人在这段时间 ...

  6. Win32多线程编程(3) — 线程同步与通信

      一.线程间数据通信 系统从进程的地址空间中分配内存给线程栈使用.新线程与创建它的线程在相同的进程上下文中运行.因此,新线程可以访问进程内核对象的所有句柄.进程中的所有内存以及同一个进程中其他所有线 ...

  7. 04747_Java语言程序设计(一)_第8章_多线程

    例8.1应用程序用Thread子类实现多线程. import java.util.Date; public class Example8_1 { static Athread threadA; sta ...

  8. Java多线程编程模式实战指南(二):Immutable Object模式

    多线程共享变量的情况下,为了保证数据一致性,往往需要对这些变量的访问进行加锁.而锁本身又会带来一些问题和开销.Immutable Object模式使得我们可以在不使用锁的情况下,既保证共享变量访问的线 ...

  9. Python 多线程、多进程 (一)之 源码执行流程、GIL

    Python 多线程.多进程 (一)之 源码执行流程.GIL Python 多线程.多进程 (二)之 多线程.同步.通信 Python 多线程.多进程 (三)之 线程进程对比.多线程 一.python ...

随机推荐

  1. Java框架之SpringMVC 05-拦截器-异常映射-Spring工作流程

    SpringMVC 拦截器 Spring MVC也可以使用拦截器对请求进行拦截处理,可以自定义拦截器来实现特定的功能,自定义的拦截器可以实现HandlerInterceptor接口中的三个方法,也可以 ...

  2. 【Java面试】java基础篇

    总结个人的面试经历以及一些网上的的面试题,以供以后面试与巩固java基础. 1.String.StringBuilder和StringBuffer的区别 String用于存储不可变字符串的类,Stri ...

  3. linux ftp 服务器 ,web 服务器 配置

    ftp服务器配置: 1:vsftpd.conf 2;vsftpd.ftpusers 3:vsftpd.user_list web服务器配置: apache 配置

  4. WEB Node-JS 服务器搭建

    一.创建express 1.创建一个单独文件 2.打开命令面板,进入该文件 3.npm config set registry = https://registry.npm.taobao.org(设置 ...

  5. Arduino系列之pwm控制LED灯(呼吸灯)

    下面我将写出最简单控制呼吸灯的方法 void setup()                                 // { pinMode(12,OUTPUT);             ...

  6. Java 设置Excel自适应行高、列宽

    在excel中,可通过设置自适应行高或列宽自动排版,是一种比较常用的快速调整表格整体布局的方法.设置自适应时,可考虑2种情况: 1.固定数据,设置行高.列宽自适应数据(常见的设置自适应方法) 2.固定 ...

  7. 【C/C++】概念: VC虚函数布局引发的问题

    在网上看到一个非常热的帖子,里面是这样的一个问题: 在打印的时候发现pFun的地址和 &(Base::f)的地址竟然不一样太奇怪了?经过一番深入研究,终于把这个问题弄明白了.下面就来一步步进行 ...

  8. js小练习---实现红绿灯

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. Codeforces 1138B Circus (构造方程+暴力)

    题意: 给你两个01串,要你选n/2个位置,使得选的位置在s1中"1"的数量等于未选的s2中"1"的数量 n<=5000,1s 思路: 设两个串中出现&q ...

  10. css 浏览兼容问题及解决办法 (1)

    主流浏览器css兼容问题的总结 最近又搞了一波网站的兼容,由于要求ie浏览器还是要兼容到ie8,所以调起来还是各种蛋疼. 现在就post一些做兼容的总结,可能不够全面,但是可以告诉大家如何避过一些坑. ...