ThreadStatic属性如何工作?


138

[ThreadStatic]属性如何起作用?我以为编译器会发出一些IL来填充/检索TLS中的值,但查看反汇编似乎并没有达到该水平。

作为后续措施,如果将其放在非静态成员上会怎样?我们有一个开发人员犯了这个错误,而编译器甚至没有发出警告。

更新资料

在这里回答的第二个问题:用静态C#修改ThreadStatic


1
如果生成的IL相同(的确如此),则必须对运行时进行专门的编码,以知道在遇到此类修饰字段时如何分配和读取该值。好像是骇客:)
Rex M

Answers:


92

在.NET jit编译器中,线程静态的实现语义低于IL级别。像VB.NET和C#一样向IL发出的编译器不需要了解有关Win32 TLS的任何知识,就可以发出可以读取和写入具有ThreadStatic属性的变量的IL代码。就C#而言,变量没有什么特别的-它只是读写内容的位置。它具有属性的事实与C#无关。C#仅需要知道针对该符号名称发出IL读或写指令。

“繁重的工作”由核心CLR完成,它负责使IL在特定的硬件体系结构上工作。

这也可以解释为什么将属性放在不适当的(非静态)符号上不会引起编译器的反应。编译器不知道该属性需要什么特殊语义。但是,诸如FX / Cop之类的代码分析工具应该对此有所了解。

另一种查看方式:CIL定义了一组存储范围:静态(全局)存储,成员存储和堆栈存储。TLS不在该列表中,很可能是因为TLS不必在该列表中。如果用符号TLS属性标记符号后,如果IL读和写指令足以访问TLS,为什么IL对TLS有特殊的表示或处理?不需要


但是,这种特殊的,特定于实现的TLS行为不会完全颠覆.NET / CLR的“可验证”卖点吗?

116

[ThreadStatic]属性如何工作?

您可以认为标记为ThreadStatic的字段已附加到线程上,并且其生存期与线程的生存期相当。

因此,在伪代码ThreadStatic中(从语义上)类似于将键值附加到线程上:

Thread.Current["MyClass.myVariable"] = 1;
Thread.Current["MyClass.myvariable"] += 1;

但是语法稍微容易一些:

class MyClass {
  [ThreadStatic]
  static int myVariable;
}
// .. then
MyClass.myVariable = 1;
MyClass.myVariable += 1;

如果将其放在非静态成员上会怎样?

我相信它被忽略了:

    class A {
        [ThreadStatic]
        public int a;
    }
    [Test]
    public void Try() {
        var a1 = new A();
        var a2 = new A();
        a1.a = 5;
        a2.a = 10;
        a1.a.Should().Be.EqualTo(5);
        a2.a.Should().Be.EqualTo(10);
    }

另外值得一提的是ThreadStatic,与普通静态字段相比,它不需要任何同步机制(因为不共享状态)。


1
第二种伪代码应该是"MyClass.myVariable",不是吗?
akshay2000

我不确定确切的限制,但我只是想指出一下它是否不一定是原始类型是否很明显。如果你看一下源TransactionScope它们存储各种各样的东西在那里的范围(referencesource.microsoft.com/#System.Transactions/System/...
Simon_Weaver

10

[ThreadStatic]在每个线程中创建同一变量的隔离版本。

例:

[ThreadStatic] public static int i; // Declaration of the variable i with ThreadStatic Attribute.

public static void Main()
{
    new Thread(() =>
    {
        for (int x = 0; x < 10; x++)
        {
            i++;
            Console.WriteLine("Thread A: {0}", i); // Uses one instance of the i variable.
        }
    }).Start();

    new Thread(() =>
   {
       for (int x = 0; x < 10; x++)
       {
           i++;
           Console.WriteLine("Thread B: {0}", i); // Uses another instance of the i variable.
       }
   }).Start();
}

3

[ThreadStatic]有的字段是在线程本地存储上创建的,因此每个线程都有其自己的字段副本,即字段的范围是线程本地的。

TLS字段可通过gs / fs段寄存器进行访问.OS内核使用这些段来访问特定于线程的内存.NET编译器不会发出任何IL来填充/检索TLS中的值。它是由OS内核完成的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.