.Net - 线程本地变量(存储)的使用
关于C#多线程的文章,大部分都在讨论线程的开始与停止或者是多线程同步问题。多线程同步就是在不同线程中访问同一个变量或共享资源,众所周知在不使用线程同步的机制下,由于竞争的存在会使某些线程产生脏读或者是覆盖其它线程已写入的值(各种混乱)。
而另外一种情况就是多线程时我们想让每个线程所访问的变量只属于各自线程自身所有,这就是所谓的线程本地变量。
线程本地变量不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制,理解这点对正确使用线程本来变量至关重要,通过线程本地变量存取的数据,总是与当前线程相关。
本文重点介绍几种线程本地变量的存储方式,并简单介绍一下线程并发访问各自解决方案。
多线程同步
public class Test
{
private static object _locker = new object(); public void TryTwoThread()
{
var b = new Bag();
Action localAct = () =>
{
for (int i = ; i < ; i++)
{
lock(_locker)
{
++b.AppleNum;
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {b.AppleNum}");
}
Thread.Sleep();
}
};
Parallel.Invoke(localAct, localAct);
}
}
最容易的方法就是给共享变量的访问加个锁来解决并发问题,当然如果在分布式系统中可以使用分布式锁。上例执行结果如下图:

线程本地变量
下面介绍NET下三种线程本地存储(Thread-Local Storage)方法:ThreadStatic, LocalDataStoreSlot 和 ThreadLocal<T>
1. 使用ThreadStatic特性
线程相关的静态字段,其做法是将成员变量声明为static并打上[ThreadStatic]这个标记。ThreadStatic特性是最简单的TLS使用,且只支持静态字段,只需要在字段上标记这个特性就可以了
//TLS中的str变量
[ThreadStatic]
private static string str = "hehe"; static void Main()
{
//另一个线程只会修改自己TLS中的str变量
Thread th = new Thread(() => { str = "Mgen"; Display(); });
th.Start();
th.Join();
Display();
} static void Display()
{
Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, str);
}
运行结果:
Mgen
hehe
可以看到,str静态字段在两个线程中都是独立存储的,互相不会被修改。
2. 数据槽
显然ThreadStatic特性只支持静态字段太受限制了,.NET线程类型中的LocalDataStoreSlot提供更好的TLS支持,但是性能不如上面介绍的ThreadStatic方法。注意:LocalDataStoreSlot有命名类型和非命名类型区分。
我们先来看看命名的LocalDataStoreSlot类型,可以通过Thread.AllocateNamedDataSlot来分配一个命名的空间,通过Thread.FreeNamedDataSlot来销毁一个命名的空间。
把线程相关的数据存储在LocalDataStoreSlot对象中,空间数据的获取和设置则通过Thread类型的GetData方法和SetData方法。
static void Main()
{
//创建Slot
LocalDataStoreSlot slot = Thread.AllocateNamedDataSlot("slot"); //设置TLS中的值
Thread.SetData(slot, "hehe"); //修改TLS的线程
Thread th = new Thread(() =>
{ Thread.SetData(slot, "Mgen");
Display();
}); th.Start();
th.Join();
Display();
//清除Slot
Thread.FreeNamedDataSlot("slot");
} //显示TLS中Slot值
static void Display()
{
LocalDataStoreSlot dataslot = Thread.GetNamedDataSlot("slot");
Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, Thread.GetData(dataslot));
}
运行结果:
Mgen
hehe
在多组件的情况下,用不同名称区分数据槽很有用。但如果不小心给不同组件起了相同的名字,则会导致数据污染。
线程同样支持未命名的LocalDataStoreSlot,未命名的LocalDataStoreSlot不需要手动清除,分配则需要Thread.AllocateDataSlot方法。
注意由于未命名的LocalDataStoreSlot没有名称,因此无法使用Thread.GetNamedDataSlot方法,只能在多个线程中引用同一个LocalDataStoreSlot才可以对TLS空间进行操作,
将上面的命名的LocalDataStoreSlot代码改成未命名的LocalDataStoreSlot执行:
//静态LocalDataStoreSlot变量
private static LocalDataStoreSlot slot; static void Main()
{
//创建Slot
slot = Thread.AllocateDataSlot(); //设置TLS中的值
Thread.SetData(slot, "hehe"); //修改TLS的线程
Thread th = new Thread(() =>
{
Thread.SetData(slot, "Mgen");
Display();
}); th.Start();
th.Join();
Display();
} //显示TLS中Slot值
static void Display()
{
Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, Thread.GetData(slot));
}
输出和上面的类似。
数据槽的性能较低,微软也不推荐使用,而且不是强类型的,用起来也不太方便。另外LocalDataStoreSlot不可能有默认值,因为初始化只能构造一个空间
3. .NET 4.0的ThreadLocal<T>类型
在.NET Framework 4.0以后新增了一种泛型化的本地变量存储机制 - ThreadLocal<T>。他的出现更大的简化了TLS的操作,下面的例子也是在之前例子基础上修改的。
对比之前代码就很好理解ThreadLocal<T>的使用,ThreadLocal<T>的构造函数接收一个lambda用于线程本地变量的延迟初始化,通过Value属性可以访问本地变量的值。
IsValueCreated可以判断本地变量是否已经创建。
private static ThreadLocal<string> local;
public ThreadLocal<string> BagLocal;
static void Main()
{
//创建ThreadLocal并提供默认值
local = new ThreadLocal<string>(() => "hehe", true); BagLocal = local; //修改TLS的线程
Thread th = new Thread(() =>
{
local.Value = "Mgen"; //通过Value属性访问
Display();
}); th.Start();
th.Join();
Display();
} //显示TLS中数据值
static void Display()
{
Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, local.Value);
}
运行结果:
Mgen
hehe
另外如果在初始化ThreadLocal<T>时,将其trackAllValues设置为true,则可以在使用ThreadLocal<T>的线程外部访问线程本地变量中所存储的值。如在测试代码中:
public void TryTwoThread()
{
var worker = new Worker();
Parallel.Invoke(worker.PutTenApple, worker.PutTenApple); //可以使用Values在线程外访问所有线程本地变量(需要ThreadLocal初始化时将trackAllValues设为true)
foreach (var tval in worker.BagLocal.Values)
{
Console.WriteLine(tval.AppleNum);
}
}
.Net - 线程本地变量(存储)的使用的更多相关文章
- 线程本地变量ThreadLocal
一.本地线程变量使用场景 并发应用的一个关键地方就是共享数据.如果你创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性.这意味 ...
- .Net学习难点讨论系列17 - 线程本地变量的使用
*:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...
- ThreadLocal 线程本地变量 及 源码分析
■ ThreadLocal 定义 ThreadLocal通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量 ...
- java线程本地变量
ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量).也许把它命名为Thre ...
- Threadlocal线程本地变量理解
转载:https://www.cnblogs.com/chengxiao/p/6152824.html 总结: 作用:ThreadLocal 线程本地变量,可用于分布式项目的日志追踪 用法:在切面中生 ...
- 深入理解java:2.4. 线程本地变量 java.lang.ThreadLocal类
ThreadLocal,很多人都叫它做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多. 可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那样每个线程可以访问自己内 ...
- 通过transmittable-thread-local源码理解线程池线程本地变量传递的原理
前提 最近一两个月花了很大的功夫做UCloud服务和中间件迁移到阿里云的工作,没什么空闲时间撸文.想起很早之前写过ThreadLocal的源码分析相关文章,里面提到了ThreadLocal存在一个不能 ...
- Java并发机制(4)--ThreadLocal线程本地变量(转)
个人理解: 说明:看了博客园中大神写的ThreadLocal的详解,感觉还是有些迷糊,下面用自己的理解简单描述下ThreadLocal的机制(难免有误): 1.首先ThreadLocal用于存储对应线 ...
- linux TLS 线程本地变量
最近在写底层hook的时候, 涉及到线程安全问题, 最开始我设计的时候使用的互斥量, 但是考虑到都是底层函数,加锁会导致性能问题, 一直在思考优化方案, 后来偶然想到,java里面有线程本地变量的AP ...
随机推荐
- Python通过xpath查找元素通过selenium读取元素信息
#coding:utf-8 from selenium import webdriver import time url ='http://www.baidu.com' driver = webdri ...
- 项目Alpha冲刺——总结
作业描述 课程: 软件工程1916|W(福州大学) 作业要求: 项目Alpha冲刺(团队) 团队名称: 火鸡堂 作业目标: 完成项目Alpha冲刺 团队信息 队名:火鸡堂 队员学号 队员姓名 博客地址 ...
- msdtc不可用
在使用“经销商园地 网上订单处理程序”等程序时,如果程序报:服务器×××上的MSDTC不可用”,可以按照以下方法进行解决: 在windows控制面版-->管理工具-->服务-->Di ...
- Scanner的常用用法
通过new Scanner(System.in)创建一个Scanner,控制台会一直等待输入,直到敲回车键结束,把所输入的内容传给Scanner. s.useDelimiter(" |,|\ ...
- Tips on Java
1.JAVA种数组的两种定义方式. int[] nums; int nums[]. 2.整型默认为int,如果需要long,须加l或L.小数默认double,d或D可省略,但如果需要float,须加f ...
- 使用WinDbg调试入门(内核模式)
windbg是一个内核模式和用户模式调试器,包含在Windows调试工具中.这里我们提供了一些实践练习,可以帮助您开始使用windbg作为内核模式调试器. 设置内核模式调试 内核模式调试环境通常有两台 ...
- Linux运维相关命令
df 查看磁盘空间大小 df命令是linux系统以磁盘分区为单位查看文件系统,可以加上参数查看磁盘剩余空间信息,命令格式: df -hl 显示格式为: 文件系统 容量 已用 ...
- HTML5 文件读取
一.定义 input的file类型会渲染为一个按钮和一段文字.点击按钮可打开文件选择窗口,文字表示对文件的描述(大部分情况下为文件名):file类型的input会有files属性,保存着文件的相关信息 ...
- 桥接:JS调用安卓方法报错Uncaught Error: Error calling method on NPObject
说一说自己粗心踩到的一个不算坑的坑: 项目是安卓webview嵌入SPA单页应用页面,涉及到JS调用原生安卓方法,但就是在调用安卓方法时死活一直报错xxx NPObject一堆错误.写了一个测试页面 ...
- Android入门教程(八)
关注我,每天都有优质技术文章推送,工作,学习累了的时候放松一下自己. 本篇文章同步微信公众号 欢迎大家关注我的微信公众号:「醉翁猫咪」 今天讲讲安卓入门(八),讲讲Android的简介,如何搭建An ...