关于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. Nginx——报错汇总

    前言 记录NGINX的错误 错误 nginx: [emerg] unknown directive "erver" in /usr/local/nginx/conf/vhost/d ...

  2. 合并K个有序链表

    方法一:采用归并的思想将链表两两合并,再将两两合并后的链表做同样的操作直到合并为只有一个新的链表为止. 归类的时间复杂度是O(logn),合并两个链表的时间复杂度是O(n),则总的时间复杂度大概是O( ...

  3. Dubbo源码分析:Filter

    类图 Filter链 在ProtocolFilterWrapper对象中完成Filter完成组建. 实现代码

  4. .NET Core入门程序及命令行练习

    用命令行一步一步新建项目.添加Package.Restore.Build.Run 执行的实现方式,更让容易让我们了解.NET Core的运行机制. 准备工作 安装.NET Core 运行环境,下载地址 ...

  5. NetHack 备忘

    NetHack 备忘 常用操作 操作均区分大小写 上下左右移动 y k u h l b j n / 查看地图上的东西 < 上楼 > 下楼 c 关门 部分怪不会开门 a 使用(工具) d 丢 ...

  6. 【JOISC2018|2019】【20190622】mergers

    题目 一\(n\)个节点的树,节点被分成\(k\)个集合,\(i\)属于\(S_i\), 一条边是可划分的当且仅当左右两边的子树不存在相同集合的点 你一次可以合并两个集合,求最少的操作次数使得所有边都 ...

  7. Docker 安装ubuntu服务器

    ### 1. 安装ubuntu ```docker pull ubuntudocker run -it -d --name ubuntu_test -p 2222:22 ubuntu ``` ### ...

  8. SpringBoot导入Excel数据到MySQL数据库

    package com.example.example1.Controller; import com.example.example1.Entity.User; import com.example ...

  9. 使用chrome浏览器调试移动端网页(非模拟访问)

    1. 使用数据线连接手机与电脑 2. 打开手机调试模式 参考:http://jingyan.baidu.com/article/f79b7cb35ada4d9145023e63.html 本人使用的m ...

  10. c语言博客作业03--循环结构

    0.展示PTA总分 1.本章学习总结 1.1学习内容总结 循环语句 for语句: for( 表达式1; 表达式2; 表达式3 ) { // 需要执行的语句; } 其执行过程是:表达式 1 首先执行且只 ...