• C#线程:本地存储


    1.[ThreadStatic]特性

    实现线程本地存储最简单的方式是在静态字段上附加ThreadStatic特性

    [ThreadStatic] static int _x;
    

    这样,每个线程都会得到一个_x的独立副本。

    但是,[ThreadStatic]并不支持实例字段(它对实例字段并不会产生任何作用);它也不支持和字段初始化器配合使用。因为它们只会在调用静态构造器的线程上执行一次。如果一定要处理实例字段,或者需要使用非默认值,则更推荐使用ThreadLocal<T>

    2.ThreadLocal<T>类

    ThreadLocal<T>对静态和实例字段都提供了线程本地存储支持,并允许指定默认值。
    例如,以下代码为每一个线程创建了一个ThreadLocal<int>对象,并将其默认值设置为3:

    static ThreadLocal<int> _x = new ThreadLocal<int>(()=>3);
    

    此后就可以调用_x的Value属性来访问线程本地值了。ThreadLocal的值是延迟计算的:其中的工厂函数会在(每一个线程)第一次调用时计算实际的值。

    ThreadLocal<T>和实例字段

    ThreadLocal<T>也支持实例字段并可以获得局部变量的值。
    例如,假设我们需要在一个多线程环境下生成随机数。但Random类不是线程安全的,因此要么在Random对象周围加锁(但是这就会限制并发性),要么为每一个线程生成一个独立的Random对象。而ThreadLocal<T>可以轻松实现第二种方案:

    var localRandom = new ThreadLocal<Random>(() 
        => new Random());
    Console.WriteLine(localRandom.Value.Next());
    

    我们在工厂函数中用最简单的方式创建了Random对象。其中,Random的无参数构造器会采用系统时钟作为随机数的种子。但如果两个Random对象是在10毫秒内创建的,则这两个对象就有可能有相同的种子。此时可以使用如下代码修正这个问题:

    var localRandom = new ThreadLocal<Random>(() 
        => new Random(Guid.NewGuid().GetHashCode()));
    Console.WriteLine(localRandom.Value.Next());
    

    3.GetData方法和SetData方法

    第三种实现线程本地存储的方式是使用Thread类的GetData和SetData方法。这些方法会将数据存储在线程独有的“插槽”(slot)中。Thread.GetData负责从线程独有的数据存储中读取数据,而Thread.SetData则向其中写入数据。这两个方法都需要使用LocalDataStoreSlot对象来获得这个插槽。所有的线程都可以获得相同的插槽,但是它们的值却是互相独立的。例如:

    class Test
    {
        // 同一个LocalDataStoreSlot对象可以跨所有线程使用。
        LocalDataStoreSlot _secSlot = Thread.GetNamedDataSlot("securityLevel");
        // 或用此方法获得匿名插槽
        //LocalDataStoreSlot _secSlot = Thread.AllocateDataSlot();
    
        // 这个属性在每个线程上都有一个单独的值。
        int SecurityLevel
        {
            get
            {
                object data = Thread.GetData(_secSlot);
                return data == null ? 0 : (int)data;
            }
            set
            {
                Thread.SetData(_secSlot, value);
            }
        }
    }
    

    在这个例子中,我们调用Thread.GetNamedDataSlot来创建一个命名插槽,这样就可以在整个应用程序中共享这个命名插槽了。此外,还可以调用Thread.AllocateDataSlot来获得一个匿名插槽,这样就可以自由控制插槽的使用范围。

    Thread.FreeNamedDataSlot方法将释放所有线程中的命名插槽。需要注意的是,只有当LocalDataStoreSlot对象的所有引用都已经在作用域之外并被垃圾回收时插槽才会释放。

    4.AsyncLocal<T>类

    到目前为止讨论的线程本地存储方案均不适用于异步函数。这是因为await之后的执行可能会恢复到其他线程中。而AsyncLocal<T>类可以跨越await保存其数据,从而解决上述问题:

    static AsyncLocal<string> _asyncLocalTest = new AsyncLocal<string>();
    static void Main()
    {
        _asyncLocalTest.Value = "test";
        await Task.Delay(1000);
        // 即使返回到另外的线程,以下操作也会执行
        Console.WriteLine(_asyncLocalTest.Value);
    }
    

    AsyncLocal<T>仍然可以将独立线程间的操作进行隔离(和线程是调用Thread.Start还是Task.Run初始化无关)。以下例子会分别输出“one one”与“two two”:

    static AsyncLocal<string> _asyncLocalTest = new AsyncLocal<string>();
    static void Main()
    {
        // 在两个并发线程上调用两次Test
        new Thread(() => Test("one")).Start();
        new Thread(() => Test("two")).Start();
        Console.ReadKey();
    }
    static async void Test(string value)
    {
        _asyncLocalTest.Value = value;
        await Task.Delay(1000);
        // 即使返回到另外的线程,以下操作也会执行
        Console.WriteLine(value + " " + _asyncLocalTest.Value);
    }
    

    与其他结构相比,AsyncLocal<T>独特而有趣的点在于:如果AsyncLocal<T>对象在线程启动时拥有值,则新的线程将“继承”这个值:

    static AsyncLocal<string> _asyncLocalTest = new AsyncLocal<string>();
    static void Main()
    {
        _asyncLocalTest.Value = "test";
        new Thread(AnotherMethod).Start();
    
        Console.ReadKey();
    }
    static void AnotherMethod()
        => Console.WriteLine(_asyncLocalTest.Value); // test
    

    新的线程实际上获得了这个值的一个副本。因此新线程对该值的修改不会影响原始值。

    static AsyncLocal<string> _asyncLocalTest = new AsyncLocal<string>();
    static void Main()
    {
        _asyncLocalTest.Value = "test";
        var t = new Thread(AnotherMethod);
        t.Start();
        t.Join();
        Console.WriteLine(_asyncLocalTest.Value); // test
    
        Console.ReadKey();
    }
    static void AnotherMethod()
        => _asyncLocalTest.Value = "no-test"; 
    

    需要注意的是新线程获得的是一个浅表副本,因此如果将AsyncLocal<string>替换为AsyncLocal<StringBuilder>AsyncLocal<List<string>>,则新线程就可以清空StringBuilder的内容或者在List<string>中添加或删除元素,而这些操作均会影响初始值。

  • 相关阅读:
    POJ 3268 Silver Cow Party (Dijkstra)
    怒学三算法 POJ 2387 Til the Cows Come Home (Bellman_Ford || Dijkstra || SPFA)
    CF Amr and Music (贪心)
    CF Amr and Pins (数学)
    POJ 3253 Fence Repair (贪心)
    POJ 3069 Saruman's Army(贪心)
    POJ 3617 Best Cow Line (贪心)
    CF Anya and Ghosts (贪心)
    CF Fox And Names (拓扑排序)
    mysql8.0的新特性
  • 原文地址:https://www.cnblogs.com/nullcodeworld/p/16661055.html
Copyright © 2020-2023  润新知