关于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. SpringBoot集成MyBatis的Bean配置方式

    SpringBoot集成MyBatis的Bean配置方式 SpringBoot是一款轻量级开发的框架,简化了很多原先的xml文件配置方式,接下来就介绍一下如何不适用XML来配置Mybatis spri ...

  2. 如何打开.ipynb文件

    1,GitHub 中可以直接打开 .ipynb 文件. 2,可以把 .ipynb 文件对应的下载链接复制到 https://nbviewer.jupyter.org/ 中查看.

  3. 转储Active Directory数据库

    获取Windows域控所有用户hash: 参考:3gstudent 方法1: 复制ntds.dit: 使用NinjaCopy,https://github.com/3gstudent/NinjaCop ...

  4. Codeforces Round #605 (Div. 3) B. Snow Walking Robot(构造)

    链接: https://codeforces.com/contest/1272/problem/B 题意: Recently you have bought a snow walking robot ...

  5. Greenplum 常用数据库管理语句,sql工具

    转载自:https://blog.csdn.net/you_xian/article/details/78549756作者:lianghc      在greenplum 使用过程中积累的一些常用查询 ...

  6. learning java NIO 之 RandomFileChannel

    import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.i ...

  7. telegraf 学习三 telegra inputs.net_response + smtp2http+ grafana 进行tcp服务状态监控

    以下演示一个简单的使用telegra inputs.net_response 进行tcp 服务状态的监控,统计集成grafana 的alert 为了方便使用了一个smtp2http 的服务,对于htt ...

  8. 洛谷 P2850 [USACO06DEC]虫洞Wormholes 题解

    P2850 [USACO06DEC]虫洞Wormholes 题目描述 While exploring his many farms, Farmer John has discovered a numb ...

  9. spark集成kerberos

    1.生成票据 1.1.创建认证用户 登陆到kdc服务器,使用root或者可以使用root权限的普通用户操作: # kadmin.local -q “addprinc -randkey spark/yj ...

  10. Linux 上配置 AG

    SQL Server Always On Availability Group 配置步骤:配置三台 Linux 集群节点创建 Availability Group配置 Cluster Resource ...