关于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 - 线程本地变量(存储)的使用的更多相关文章

  1. 线程本地变量ThreadLocal

    一.本地线程变量使用场景 并发应用的一个关键地方就是共享数据.如果你创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性.这意味 ...

  2. .Net学习难点讨论系列17 - 线程本地变量的使用

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

  3. ThreadLocal 线程本地变量 及 源码分析

    ■ ThreadLocal 定义 ThreadLocal通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量 ...

  4. java线程本地变量

      ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量).也许把它命名为Thre ...

  5. Threadlocal线程本地变量理解

    转载:https://www.cnblogs.com/chengxiao/p/6152824.html 总结: 作用:ThreadLocal 线程本地变量,可用于分布式项目的日志追踪 用法:在切面中生 ...

  6. 深入理解java:2.4. 线程本地变量 java.lang.ThreadLocal类

    ThreadLocal,很多人都叫它做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多. 可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那样每个线程可以访问自己内 ...

  7. 通过transmittable-thread-local源码理解线程池线程本地变量传递的原理

    前提 最近一两个月花了很大的功夫做UCloud服务和中间件迁移到阿里云的工作,没什么空闲时间撸文.想起很早之前写过ThreadLocal的源码分析相关文章,里面提到了ThreadLocal存在一个不能 ...

  8. Java并发机制(4)--ThreadLocal线程本地变量(转)

    个人理解: 说明:看了博客园中大神写的ThreadLocal的详解,感觉还是有些迷糊,下面用自己的理解简单描述下ThreadLocal的机制(难免有误): 1.首先ThreadLocal用于存储对应线 ...

  9. linux TLS 线程本地变量

    最近在写底层hook的时候, 涉及到线程安全问题, 最开始我设计的时候使用的互斥量, 但是考虑到都是底层函数,加锁会导致性能问题, 一直在思考优化方案, 后来偶然想到,java里面有线程本地变量的AP ...

随机推荐

  1. WinDbg常用命令系列---内存数据显示和对应符号显示d*s(dds、dps、dqs)

    命令dds, dps,  dqs显示给定范围内的内存内容.假定该内存是符号表中的一系列地址.相应的符号也会显示出来. dds [Options] [Range] dqs [Options] [Rang ...

  2. Python 06 Geany的基本使用2

    原文:https://www.cnblogs.com/denny402/p/5096001.html 功能:打开图片 1.下载库 pip install matplotlib 2.编写代码,将代码粘贴 ...

  3. [RN] React Native 下实现底部标签(支持滑动切换)

    上一篇文章 [RN] React Native 下实现底部标签(不支持滑动切换) 总结了不支持滑动切换的方法,此篇文章总结出 支持滑动 的方法 准备工作之类的,跟上文类似,大家可点击上文查看相关内容. ...

  4. GoCN每日新闻(2019-10-20)

    GoCN每日新闻(2019-10-20) slakc是如何构建共享频道的 https://slack.engineering/how-slack-built-shared-channels-8d42c ...

  5. mysql count() 函数,对结果统计计数

    mysql> ; +----------+------------+-----+---------------------+ | name_new | transactor | pid | or ...

  6. 【BZOJ 1036】 树的统计count

    题目 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: ...

  7. 微信小程序 wxs的简单应用

    Demo地址:微信小程序wxs的简单应用 案例分析 张三.李四.王五,各自分别都有数量不等的车,现在需要列表显示名字及他们拥有车的数量, list数据结构如下,当我们使用wx:for进行显示时,发现个 ...

  8. SpringDataRedis的简单案例使用

    一.SpringDataRedis环境搭建 第一步.导入坐标 <!-- 缓存 --> <dependency> <groupId>redis.clients< ...

  9. idea的maven工程中修改pom会改变项目jdk版本

    解决办法 方案一 //pom中配置maven插件时候 <plugins> <!--jdk编译插件--> <plugin> <groupId>org.ap ...

  10. mark_rabbitMQ

    一.1.6 和1.7的区别 二.63跟65好像有点差异 有些jar包问题 三.预取策略 https://blog.csdn.net/hry2015/article/details/79078312 四 ...