多平台统一管理软件接口,如何实现多平台统一管理软件接口
300
2022-06-23
1. 简介
普通共享变量:
在某个类上用静态属性的方式即可。
多线程共享变量
希望能将这个变量的共享范围缩小到单个线程内
无关系的B线程无法访问到A线程的值;
[ThreadStatic]特性、ThreadLocal
例子:
由于 .NET Core 不再实现 CallContext,所以下列代码只能在 .NET Framework 中执行
class Program
{
//对照
private static string _normalStatic;
[ThreadStatic]
private static string _threadStatic;
private static ThreadLocal
private static AsyncLocal
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
class Program
{
[ThreadStatic]
private static string _threadStatic;
private static ThreadLocal
private static AsyncLocal
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
线程池线程是可重用的,线程不会销毁,当线程被重用时,之前使用保存的值依然存在,可能造成影响
使用 AsyncLocal
线程使用后回归线程池, AsyncLocal
new Task(...) 默认不是新建一个线程,而是使用线程池线程
3. 解析 AsyncLocal
AsyncLocal
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 的实现原理》 --- 黑洞视界
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~