.Net学习难点讨论系列17 - 线程本地变量的使用
*:first-child {
margin-top: 0 !important;
}
body>*:last-child {
margin-bottom: 0 !important;
}
/* BLOCKS
=============================================================================*/
p, blockquote, ul, ol, dl, table, pre {
margin: 15px 0;
}
/* HEADERS
=============================================================================*/
h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
}
h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code {
font-size: inherit;
}
h1 {
font-size: 28px;
color: #000;
}
h2 {
font-size: 24px;
border-bottom: 1px solid #ccc;
color: #000;
}
h3 {
font-size: 18px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
color: #777;
font-size: 14px;
}
body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child {
margin-top: 0;
padding-top: 0;
}
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0;
}
h1+p, h2+p, h3+p, h4+p, h5+p, h6+p {
margin-top: 10px;
}
/* LINKS
=============================================================================*/
a {
color: #4183C4;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* LISTS
=============================================================================*/
ul, ol {
padding-left: 30px;
}
ul li > :first-child,
ol li > :first-child,
ul li ul:first-of-type,
ol li ol:first-of-type,
ul li ol:first-of-type,
ol li ul:first-of-type {
margin-top: 0px;
}
ul ul, ul ol, ol ol, ol ul {
margin-bottom: 0;
}
dl {
padding: 0;
}
dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px;
}
dl dt:first-child {
padding: 0;
}
dl dt>:first-child {
margin-top: 0px;
}
dl dt>:last-child {
margin-bottom: 0px;
}
dl dd {
margin: 0 0 15px;
padding: 0 15px;
}
dl dd>:first-child {
margin-top: 0px;
}
dl dd>:last-child {
margin-bottom: 0px;
}
/* CODE
=============================================================================*/
pre, code, tt {
font-size: 12px;
font-family: Consolas, "Liberation Mono", Courier, monospace;
}
code, tt {
margin: 0 0px;
padding: 0px 0px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px;
}
pre>code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent;
}
pre {
background-color: #f8f8f8;
border: 1px solid #ccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px;
}
pre code, pre tt {
background-color: transparent;
border: none;
}
kbd {
-moz-border-bottom-colors: none;
-moz-border-left-colors: none;
-moz-border-right-colors: none;
-moz-border-top-colors: none;
background-color: #DDDDDD;
background-image: linear-gradient(#F1F1F1, #DDDDDD);
background-repeat: repeat-x;
border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD;
border-image: none;
border-radius: 2px 2px 2px 2px;
border-style: solid;
border-width: 1px;
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
line-height: 10px;
padding: 1px 4px;
}
/* QUOTES
=============================================================================*/
blockquote {
border-left: 4px solid #DDD;
padding: 0 15px;
color: #777;
}
blockquote>:first-child {
margin-top: 0px;
}
blockquote>:last-child {
margin-bottom: 0px;
}
/* HORIZONTAL RULES
=============================================================================*/
hr {
clear: both;
margin: 15px 0;
height: 0px;
overflow: hidden;
border: none;
background: transparent;
border-bottom: 4px solid #ddd;
padding: 0;
}
/* TABLES
=============================================================================*/
table th {
font-weight: bold;
}
table th, table td {
border: 1px solid #ccc;
padding: 6px 13px;
}
table tr {
border-top: 1px solid #ccc;
background-color: #fff;
}
table tr:nth-child(2n) {
background-color: #f8f8f8;
}
/* IMAGES
=============================================================================*/
img {
max-width: 100%
}
-->
关于C#多线程的文章,大部分都在讨论线程的起停或者是多线程同步问题。多线程同步就是在不同线程中访问同一个变量(一般是线程工作函数外部的变量),众所周知在不使用线程同步的机制下,由于竟态的存在会使某些线程产生脏读或者是覆盖其它线程已写入的值(各种混乱)。而另外一种情况就是我们想让线程所访问的变量属于线程自身所有,这就是所谓的线程本地变量。
下文我们将逐渐扩展一个最简单的示例代码,来展示上面所说的变量并发访问以及线程本地变量的区别和各自解决方案。
这里要展示的例子很简单。所访问的变量是一个“袋子内苹果的数量”,而工作函数就是“往袋子里放苹果”。
public class Bag
{
public int AppleNum { get; set; }
}
public class Test
{
public void TryTwoThread()
{
var b = new Bag();
Action localAct = () =>
{
for (int i = 0; i < 10; i++)
{
++b.AppleNum;
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {b.AppleNum}");
Thread.Sleep(100);
}
};
Parallel.Invoke(localAct, localAct);
}
}
// Program.cs
var tester = new Test();
tester.TryTwoThread();
如代码所示,这是一段经典的多线程变量并发访问错误的代码。由于没有任何并发访问控制的代码,所以执行结果是不确定的。我们期望的结果是有20个苹果在袋子种,实际情况下很难达到这个结果。
由于执行结果不确定,所以上面只是展示了其中一种随机出现的情况。
解决这个问题的方法就是使用并发控制,最容易的方法就是给共享变量的访问加个锁。
public class Test
{
private object _locker = new object();
public void TryTwoThread()
{
var b = new Bag();
Action localAct = () =>
{
for (int i = 0; i < 10; i++)
{
lock(_locker)
{
++b.AppleNum;
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {b.AppleNum}");
}
Thread.Sleep(100);
}
};
Parallel.Invoke(localAct, localAct);
}
}
这样执行结果就能得到保障,最终袋子里就会有20个苹果。当然还有其它并发控制方法,但那不是本文重点忽略不说。
在某些场景下我们会有另一种需求,我们关心的是每个线程往袋子里放了多少个苹果。这时我们就需要让Bag对象与线程相关(有多个袋子,每个袋子为线程所有)。这就需要用到本文重点要介绍的内容 - 线程本地变量。
在不使用线程本地变量的情况下,实现上述目的的一个简单方法是把变量放入工作函数内部,作为函数内部变量。
public class Test
{
public void TryTwoThread()
{
Action localAct = () =>
{
var b = new Bag(); //把变量访问工作函数当中
for (int i = 0; i < 10; i++)
{
++b.AppleNum;
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {b.AppleNum}");
Thread.Sleep(100);
}
};
Parallel.Invoke(localAct, localAct);
}
}
可以看到结果如我们所愿。
如果我们的工作函数是独立于一个类中,且要并发的访问的变量是这个类的成员,上面这种方法就不适用了。
前面的例子种的Action换成如下的工作类:
public class Worker
{
private Bag _bag = new Bag();
public void PutTenApple()
{
for (int i = 0; i < 10; i++)
{
PutApple();
Show();
Thread.Sleep(100);
}
}
private void PutApple()
{
++_bag.AppleNum;
}
private void Show()
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {_bag.AppleNum}");
}
}
测试方法改为:
public void TryTwoThread()
{
var worker = new Worker();
Parallel.Invoke(worker.PutTenApple, worker.PutTenApple);
}
注意上面的Worker类也是一个不满足我们每个线程独立操作自己关联变量要求的例子。而且由于没有并发控制,程序的执行结果不可控。
我们也可以将
_bag
变量声明于PutTenApple
中来实现与线程本地变量一样的效果,但那样在调用PutApple
和Show
方法时就免不了传参数。
下面开始介绍几种实现线程本地变量的方法。
线程相关的静态字段
第一种方法线程相关的静态字段是使用ThreadStatic
Attribute。这也是微软推荐的性能更好的方法。
其做法是将成员变量声明为static
并打上[ThreadStatic]
这个标记。我们在之前代码的基础上做如下修改:
[ThreadStatic] private static Bag _bag = new Bag();
注意这个实现是有问题的。下面会详细介绍。
如果你的VS上也安装有Resharper这个宇宙级插件,你会看到在初始化这个静态变量的代码下会有这样的提示:
关于这个提示,ReSharper官网也有解释。
简单来说,就是上面的初始化器只会被调用一次,导致的结果就是只有第一个执行此方法的线程能正确获取到_bag
成员的值,之后的进程再访问_bag
时,会发现_bag
仍是未初始化状态 - 为null。
对于这个问题我选择的解决方式是在工作方法中去初始化_bag
变量。
public class Worker
{
[ThreadStatic] private static Bag _bag;
public void PutTenApple()
{
_bag = new Bag(); //调用前初始化
for (int i = 0; i < 10; i++)
{
PutApple();
Show();
Thread.Sleep(100);
}
}
private void PutApple()
{
++_bag.AppleNum;
}
private void Show()
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {_bag.AppleNum}");
}
}
ReSharper网站给出的方法是通过一个属性去包装这个静态字段,并将对静态字段的访问都换成对静态属性的访问。
public class Worker
{
[ThreadStatic] private static Bag _bag;
public static Bag Bag => _bag ?? (_bag = new Bag());
public void PutTenApple()
{
for (int i = 0; i < 10; i++)
{
PutApple();
Show();
Thread.Sleep(100);
}
}
private void PutApple()
{
++Bag.AppleNum;
}
private void Show()
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {Bag.AppleNum}");
}
}
对于线程本地变量,如果在线程外访问,会发现它并没有受到线程操作的影响。
public void TryTwoThread()
{
var worker = new Worker();
Parallel.Invoke(worker.PutTenApple, worker.PutTenApple);
Console.WriteLine($"Main Thread : {Thread.CurrentThread.ManagedThreadId} - {Worker.Bag.AppleNum}");
}
主线程中访问情况:
数据槽
另一种等价的方法是使用LocalDataStoreSlot
,但是性能不如上面介绍的ThreadStatic
方法。
public class Worker
{
private LocalDataStoreSlot _localSlot = Thread.AllocateDataSlot();
public void PutTenApple()
{
Thread.SetData(_localSlot, new Bag());
for (int i = 0; i < 10; i++)
{
PutApple();
Show();
Thread.Sleep(100);
}
}
private void PutApple()
{
var bag = Thread.GetData(_localSlot) as Bag;
++bag.AppleNum;
}
private void Show()
{
var bag = Thread.GetData(_localSlot) as Bag;
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {bag.AppleNum}");
}
}
把线程相关的数据存储在LocalDataStoreSlot
对象中,并通过Thread
的GetData
和SetData
进行存取。
数据槽还有一种命名的分配方式:
private LocalDataStoreSlot _localSlot = Thread.AllocateNamedDataSlot("Apple");
public void PutTenApple()
{
_localSlot = Thread.GetNamedDataSlot("Apple");//演示用
Thread.SetData(_localSlot, new Bag());
for (int i = 0; i < 10; i++)
{
PutApple();
Show();
Thread.Sleep(100);
}
}
在多组件的情况下,用不同名称区分数据槽很有用。但如果不小心给不同组件起了相同的名字,则会导致数据污染。
数据槽的性能较低,微软也不推荐使用,而且不是强类型的,用起来也不太方便。
.NET 4 - ThreadLocal
在.NET Framework 4以后新增了一种泛型化的本地变量存储机制 - ThreadLocal<T>
。下面的例子也是在之前例子基础上修改的。对比之前代码就很好理解ThreadLocal<T>
的使用,ThreadLocal<T>
的构造函数接收一个lambda用于线程本地变量的延迟初始化,通过Value属性可以访问本地变量的值。IsValueCreated可以判断本地变量是否已经创建。
public class Worker
{
private ThreadLocal<Bag> _bagLocal = new ThreadLocal<Bag>(()=> new Bag(), true);
public ThreadLocal<Bag> BagLocal => _bagLocal;
public void PutTenApple()
{
if (_bagLocal.IsValueCreated) //在第一次访问后,线程本地变量才会被创建
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - 已初始化");
}
for (int i = 0; i < 10; i++)
{
PutApple();
Show();
Thread.Sleep(100);
}
if (_bagLocal.IsValueCreated)
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - 已初始化");
}
}
private void PutApple()
{
var bag = _bagLocal.Value; //通过Value属性访问
++bag.AppleNum;
}
private void Show()
{
var bag = _bagLocal.Value; //通过Value属性访问
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {bag.AppleNum}");
}
}
另外如果在初始化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学习难点讨论系列17 - 线程本地变量的使用的更多相关文章
- 线程本地变量ThreadLocal源码解读
一.ThreadLocal基础知识 原始线程现状: 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步.但是Spring中的各种模板 ...
- 线程本地变量ThreadLocal
一.本地线程变量使用场景 并发应用的一个关键地方就是共享数据.如果你创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性.这意味 ...
- ThreadLocal 线程本地变量 及 源码分析
■ ThreadLocal 定义 ThreadLocal通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量 ...
- .Net - 线程本地变量(存储)的使用
关于C#多线程的文章,大部分都在讨论线程的开始与停止或者是多线程同步问题.多线程同步就是在不同线程中访问同一个变量或共享资源,众所周知在不使用线程同步的机制下,由于竞争的存在会使某些线程产生脏读或者是 ...
- Threadlocal线程本地变量理解
转载:https://www.cnblogs.com/chengxiao/p/6152824.html 总结: 作用:ThreadLocal 线程本地变量,可用于分布式项目的日志追踪 用法:在切面中生 ...
- linux TLS 线程本地变量
最近在写底层hook的时候, 涉及到线程安全问题, 最开始我设计的时候使用的互斥量, 但是考虑到都是底层函数,加锁会导致性能问题, 一直在思考优化方案, 后来偶然想到,java里面有线程本地变量的AP ...
- 线程本地变量ThreadLocal (耗时工具)
线程本地变量类 package king; import java.util.ArrayList; import java.util.List; import java.util.Map; impor ...
- 线程本地变量ThreadLocal (耗时工具)【原】
线程本地变量类 package king; import java.util.ArrayList; import java.util.List; import java.util.Map; impor ...
- java线程本地变量
ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量).也许把它命名为Thre ...
随机推荐
- spring boot + neo4j restful
整整折腾了三天,终于把spring boot + neo4j的路走通了. 这里介绍3个部分,pom,entity,repository 1)pom <?xml version="1.0 ...
- 卷积神经网络(CNN)新手指南 1
http://blog.csdn.net/real_myth/article/details/52273930 卷积神经网络(CNN)新手指南 2016-07-29 18:22 Blake 1条评论 ...
- posix第二篇-----linux 锁机制
1 简介 锁机制(lock) 是多线程编程中最常用的同步机制,用来对多线程间共享的临界区(Critical Section) 进行保护. Pthreads提供了多种锁机制,常见的有: 1) Mutex ...
- HDU 5611 Baby Ming and phone number
#include<cstdio> #include<cstring> #include<vector> #include<cmath> #include ...
- Nginx rewrite 规则 与 proxy_pass 实现
Nginx rewrite 规则 与 proxy_pass 实现 -------------------------------------------------------------- ...
- (中等) POJ 1703 Find them, Catch them,带权并查集。
Description The police office in Tadu City decides to say ends to the chaos, as launch actions to ro ...
- [Unity Asset]AssetBundle系列——游戏资源打包
转载:http://www.cnblogs.com/sifenkesi/p/3557231.html 将本地资源打包,然后放到资源服务器上供游戏客户端下载或更新.服务器上包含以下资源列表:(1)游戏内 ...
- Sematic库系列一
最近在做项目时采用了sematic css 库,由于这个库的资料太少,在做项目中遇到很多问题,在这里做一些记录 1. 下拉框demo HTML 代码 <div class="field ...
- lPC1788的串口通讯
#ifndef __DEBUGSERIAL_H_ #define __DEBUGSERIAL_H_ #include "sys.h" #include "stdio.h& ...
- 【repost】JS中的hook机制
hook机制也就是钩子机制,由表驱动实现,常用来处理多种特殊情况的处理.我们预定义了一些钩子,在常用的代码逻辑中去适配一些特殊的事件,这样可以让我们少些很多if else语句.举个高考加分的例子,比如 ...