多线程共享变量和 AsyncLocal
1. 简介
- 普通共享变量:
- 在某个类上用静态属性的方式即可。
- 多线程共享变量
- 希望能将这个变量的共享范围缩小到单个线程内
- 无关系的B线程无法访问到A线程的值;
[ThreadStatic]特性、ThreadLocal<T> 、CallContext 、AsyncLocal<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 属性的真正的数据存取是通过 ExecutionContext 的internal的方法GetLocalValue和SetLocalValue将数据存到 当前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 的实现将会在 ExecutionContext 的 SetLocalValue 方法中被不断替换。
- 查询的时间复杂度和空间复杂度依次递增
3.2. 结论
AsyncLocal类型存储数据,是在自己线程的 ExecutionContext 中- ExecutionContext 的实例会随着异步或者多线程的启动而被流向执行后续代码的其他线程,保证了启动异步的线程存储的数据可以被访问到
- 数据存到
IAsyncLocalValueMap类型的变量中,此变量会根据存储的AsyncLocal变量个数而切换实现- 支持存储量越大的实现类型,性能越差
参考资料:
《浅析 .NET 中 AsyncLocal 的实现原理》 --- 黑洞视界
多线程共享变量和 AsyncLocal的更多相关文章
- 『Python』 多线程 共享变量的实现
简介: 对于Python2而言,对于一个全局变量,你的函数里如果只使用到了它的值,而没有对其赋值(指a = XXX这种写法)的话,就不需要声明global. 相反,如果你对其赋了值的话,那么你就需要声 ...
- Java多线程共享变量控制
1. 可见性 如果一个线程对共享变量值的修改,能够及时的被其他线程看到,叫做共享变量的可见性.如果一个变量同时在多个线程的工作内存中存在副本,那么这个变量就叫共享变量 2. JMM(java内存模型) ...
- EntityFrameworkCore之工作单元的封装
1. 简介 2. DbContext 生命周期和使用规范 2.1. 生命周期 2.2. 使用规范 2.3. 避免 DbContext 线程处理问题 3. 封装-工作单元 3.1. 分析 3.2. 设计 ...
- 谈谈.NET Core下如何利用 AsyncLocal 实现共享变量
前言 在Web 应用程序中,我们经常会遇到这样的场景,如用户信息,租户信息本次的请求过程中都是固定的,我们希望是这种信息在本次请求内,一次赋值,到处使用.本文就来探讨一下,如何在.NET Core 下 ...
- 【.NET深呼吸】基于异步上下文的本地变量(AsyncLocal)
在开始吹牛之前,老周说两个故事. 第一个故事是关于最近某些别有用心的人攻击.net的事,其实我们不用管它们,只要咱们知道自己是.net爱好者就行了,咱们就是因为热爱.net才会选择它.这些人在这段时间 ...
- Win32多线程编程(3) — 线程同步与通信
一.线程间数据通信 系统从进程的地址空间中分配内存给线程栈使用.新线程与创建它的线程在相同的进程上下文中运行.因此,新线程可以访问进程内核对象的所有句柄.进程中的所有内存以及同一个进程中其他所有线 ...
- 04747_Java语言程序设计(一)_第8章_多线程
例8.1应用程序用Thread子类实现多线程. import java.util.Date; public class Example8_1 { static Athread threadA; sta ...
- Java多线程编程模式实战指南(二):Immutable Object模式
多线程共享变量的情况下,为了保证数据一致性,往往需要对这些变量的访问进行加锁.而锁本身又会带来一些问题和开销.Immutable Object模式使得我们可以在不使用锁的情况下,既保证共享变量访问的线 ...
- Python 多线程、多进程 (一)之 源码执行流程、GIL
Python 多线程.多进程 (一)之 源码执行流程.GIL Python 多线程.多进程 (二)之 多线程.同步.通信 Python 多线程.多进程 (三)之 线程进程对比.多线程 一.python ...
随机推荐
- MySQL日志及索引
MySQL物理结构: MySQL它是通过文件系统对数据进行储存和管理,从物理结构上分为日志文件和数据文件 日志文件: 日志文件记录了数据库操作的信息和一些错误信息,我们常用的日志文件有:错误日志.二进 ...
- 13、python的路径处理
前言:本文主要介绍python中路径的处理,包括os模块和有关的2个魔法变量. 一.os模块 python里面的os模块有许多方法可以让我们通过代码实现创建,删除和更改目录,具体如下: os.getc ...
- 深入浅出WPF笔记
数据层(Database,Oracle等) 业务逻辑层(Service,Data Access Layer,WCF) 表示层(WPF,Win Form,ASP.net,Silverlight) [WP ...
- Intellij IDEA中将打开的代码与类文件进行关联
Intellij IDEA中在导航栏打开多个类时,如何快速定位到某个类所在的目录? 经过研究,可以通过以下设置完成: 设置完成后,点击导航栏中的类,左边的Project导航就可以快速定位到该类所在的目 ...
- mysql安装教程linux
https://www.cnblogs.com/YangshengQuan/p/8431520.html 设置sql远程访问
- 工具之grep
转自:http://www.cnblogs.com/dong008259/archive/2011/12/07/2279897.html grep (global search regular exp ...
- linux--->PHP常用模块解析
PHP常用模块解析 php-gd :GD库是php处理图形的扩展库,GD库提供了一系列用来处理图片的API,使用GD库可以处理图片,或者生成图片,也可以给图片加水印 php-ldap :LDAP是轻量 ...
- V8垃圾回收?看这篇就够了!
什么是内存管理 内存管理是控制和协调应用程序访问电脑内存的过程.这个过程是复杂的,对于我们来说,可以说相当于一个黑匣子. 当咱们的应用程序运行在某个操作系统中的时候,它访问电脑内存(RAM)来达成下列 ...
- Pileup 格式详细说明
转自: https://blog.csdn.net/herokoking/article/details/79276939 Pileup 格式最初是由Sanger Institute的Tony Cox ...
- Spring Boot 2.x基础教程:默认数据源Hikari的配置详解
通过上一节的学习,我们已经学会如何应用Spring中的JdbcTemplate来完成对MySQL的数据库读写操作.接下来通过本篇文章,重点说说在访问数据库过程中的一个重要概念:数据源(Data Sou ...