>>返回《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. 异数OS谈发展国产操作系统的问题

    异数OS谈发展国产操作系统的问题 为什么写本文 最近中兴被美制裁的问题以及红芯使用开源技术宣称国产自主技术引发了舆论不少对国产CPU以及国产操作系统自主技术的讨论,为什么我们国家有BAT,有原子弹,能 ...

  2. [bzoj4825] [loj#2018] [Hnoi2017] 单旋

    Description \(H\) 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(\(splay\))是一种数据 结构,因为代码好写,功能多,效率高,掌握这种数据结构 ...

  3. Stream排序Map集合

    最近小编自己一个人在负责一个项目的后台开发,其中有一部分是统计相关的功能,所以需要一些排序或者分组的操作,之前这种操作小编觉得还是比较麻烦的,虽热有一些现成的工具类,但是工具类的写法也是比较复杂的,但 ...

  4. 啥叫ORM

    名字: object / relation map 对象关系映射 定义: 通过(描述对象和数据库之间映射的)元数据把对象自动转为关系数据 一般都是作为中间件 优缺: 优点是自动化,屏蔽了SQL语句,而 ...

  5. 使用静态URL片段 URL路由 精通ASP-NET-MVC-5-弗瑞曼

  6. POJ Protecting the Flowers

    点击打开题目 题目大意 奶牛要吃花,FJ来赶牛,将第i头牛赶走要2*ti分钟,奶牛每分钟吃di个单位花,求花的最小损失 先赶吃花多的,Wrong Answer QAQ 我们可以算一算损失 设sum=d ...

  7. GP工作室—Alpha版本发布2

    GP工作室-Alpha版本发布2 一.简介 1.1作业要求 这个作业属于哪个课程 https://edu.cnblogs.com/campus/xnsy/GeographicInformationSc ...

  8. python实例:自动爬取豆瓣读书短评,分析短评内容

    思路: 1.打开书本“更多”短评,复制链接 2.脚本分析链接,通过获取短评数,计算出页码数 3.通过页码数,循环爬取当页短评 4.短评写入到txt文本 5.读取txt文本,处理文本,输出出现频率最高的 ...

  9. CUDA学习(四)之使用全局内存进行归约求和(一个包含N个线程的线程块)

    问题:使用CUDA进行数组元素归约求和,归约求和的思想是每次循环取半. 详细过程如下: 假设有一个包含8个元素的数组,索引下标从0到7,现通过3次循环相加得到这8个元素的和,使用一个间隔变量,该间隔变 ...

  10. ImageButton去边框&Button或者ImageButton的背景透明

    在ImageButton中载入图片后,很多人会觉得有图片周围的白边会影响到美观,其实解决这个问题有两种方法 一种方法是将ImageButton的背景改为所需要的图片.如:android:backgro ...