.Net - 线程本地变量(存储)的使用

关于C#多线程的文章,大部分都在讨论线程的开始与停止或者是多线程同步问题。多线程同步就是在不同线程中访问同一个变量或共享资源,众所周知在不使用线程同步的机制下,由于竞争的存在会使某些线程产生脏读或者是覆盖其它线程已写入的值(各种混乱)。

而另外一种情况就是多线程时我们想让每个线程所访问的变量只属于各自线程自身所有,这就是所谓的线程本地变量

线程本地变量不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制,理解这点对正确使用线程本来变量至关重要, 通过线程本地变量存取的数据,总是与当前线程相关。

本文重点介绍几种线程本地变量的存储方式,并简单介绍一下线程并发访问各自解决方案。

多线程同步

public class Test
{
    private static 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);
    }
}

最容易的方法就是给共享变量的访问加个锁来解决并发问题,执行结果如下图:

线程本地变量

下面介绍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);
}

运行结果:

3 Mgen
1 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));
}

运行结果:

3 Mgen
1 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);
}

运行结果:

3 Mgen
1 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);
    }
}

原文地址:https://www.cnblogs.com/li150dan/p/11445392.html

时间: 2024-10-11 10:10:21

.Net - 线程本地变量(存储)的使用的相关文章

.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; } /* HEAD

线程本地变量ThreadLocal

一.本地线程变量使用场景 并发应用的一个关键地方就是共享数据.如果你创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性.这意味着,如果你在一个线程里改变一个属性,全部的线程都会受到这个改变的影响. 有时,你希望程序里的各个线程的属性不会被共享. Java 并发 API提供了一个很清楚的机制叫本地线程变量即ThreadLocal. 模拟ThreadLocal类实现:线程范围内的共享变量,每个线程只能访问他自己的,不能访问别的

线程本地变量ThreadLocal源码解读

  一.ThreadLocal基础知识   原始线程现状: 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步.但是Spring中的各种模板类并未采用线程同步机制,因为线程同步会影响并发性和系统性能,而且实现难度也不小. ThreadLocal在Spring中发挥着重要的作用.在管理request作用域的bean,事务管理,任务调度,AOP等模块中都出现了它的身影. ThreadLocal介绍: 它不是一个线程,而是线程的一个本地化

线程本地变量的使用

Net学习难点讨论系列17 - 线程本地变量的使用 关于C#多线程的文章,大部分都在讨论线程的起停或者是多线程同步问题.多线程同步就是在不同线程中访问同一个变量(一般是线程工作函数外部的变量),众所周知在不使用线程同步的机制下,由于竟态的存在会使某些线程产生脏读或者是覆盖其它线程已写入的值(各种混乱).而另外一种情况就是我们想让线程所访问的变量属于线程自身所有,这就是所谓的线程本地变量.下文我们将逐渐扩展一个最简单的示例代码,来展示上面所说的变量并发访问以及线程本地变量的区别和各自解决方案. 这

java线程本地变量

ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量).也许把它命名为ThreadLocalVar更加合适.线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突. 从线程的角度看,每个线程都保持一个对其线程局部变量副本

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

转自:博客园-海子-http://www.cnblogs.com/dolphin0520/p/3920407.html Java并发编程:深入剖析ThreadLocal 首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.各个线程中访问的是不同的对象. 另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal

深入理解线程本地变量ThreadLocal

ThreadLocal理解: 如果在多线程并发环境中,一个可变对象涉及到共享与竞争,那么该可变对象就一定会涉及到线程间同步操作,这是多线程并发问题. 否则该可变对象将作为线程私有对象,可通过ThreadLocal进行管理,实现线程间私有对象隔离的目的. 可以发现,ThreadLocal并没有解决多线程并发的问题,因为ThreadLocal管理的可变对象的性质本来就不会涉及到多线程并发而引发的共享.竞争和同步问题,使用ThreadLocal管理只是方便了多线程获取和使用该私有可变对象的途径和方式.

JAVA线程本地变量ThreadLocal和私有变量的区别

ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些. 所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及. ThreadLocal的接口方法 ThreadLocal类接口很简单,只有4个方法,我们先来了解一下: void set(Object value) public void remove() 将当前线程局部变量的值删除,目的是为了减

并发组件之一:ThreadLocal线程本地变量

一.简介  ThreadLocal从字面上进行理解很容易被大部分人认为是本地线程,然而ThreadLocal并不是一个Thread,可以说它只是一个容器,而它装的内容又是Thread的局部变量.很多文章都会把ThreadLocal当作是解决高并发下线程不安全的一种做法,然而ThreadLocal并不是为了解决并发安全甚至可以这么说,它与真正的并发安全背道而驰.并发安全是指多个线程对同一个对象进行操作而导致的不安全,但是ThreadLocal在每个线程内部保存了一份该对象,使得每个线程都操作自己内