Как работает атрибут ThreadStatic?

140

Как работает [ThreadStatic]атрибут? Я предполагал, что компилятор испускает некоторый IL для заполнения / извлечения значения в TLS, но, глядя на разборку, похоже, что это не делается на этом уровне.

Что произойдет, если вы поместите его на нестатический член? У нас был разработчик, который допустил эту ошибку, а компилятор даже не выдает предупреждения.

Обновить

Ответ на второй вопрос здесь: ThreadStatic изменен с помощью Static C #

Джошперри
источник
1
Если сгенерированный IL такой же (что действительно так), тогда среда выполнения должна быть специально закодирована, чтобы знать, как выделить и прочитать значение, когда оно попадает в такое украшенное поле. Похоже на хак :)
Rex M
Обратите внимание, что если ThreadStatic используется с потоком пула потоков, значение все равно может быть установлено в следующий раз, когда этот поток пула будет повторно использован (если ОС не гарантирует, что он повторно инициализирует локальное хранилище потока, когда поток пула повторно используется ). В целях безопасности при запуске потока пула инициализируйте все ThreadStatics в желаемое состояние. Чтобы упростить эту задачу, соберите ThreadStatics в класс MyContext, чтобы повторно инициализировать только одну статику: public class SomeClass { public static MyContext myContext; ...теперь сразу после захвата потока пула выполнитеSomeClass.myContext = new MyContext();
ToolmakerSteve

Ответы:

94

Семантика реализации статических потоков ниже уровня IL в jit-компиляторе .NET. Компиляторам, которые отправляют данные в IL, такие как VB.NET и C #, не нужно ничего знать о Win32 TLS, чтобы генерировать код IL, который может читать и записывать переменную с атрибутом ThreadStatic. Насколько известно C #, в этой переменной нет ничего особенного - это просто место для чтения и записи. Тот факт, что он имеет атрибут, не имеет значения для C #. C # нужно только знать, чтобы выдавать инструкции чтения или записи IL для этого имени символа.

«Тяжелая работа» выполняется основной средой CLR, которая отвечает за работу IL на конкретной аппаратной архитектуре.

Это также объясняет, почему размещение атрибута в несоответствующем (нестатическом) символе не вызывает реакции со стороны компилятора. Компилятор не знает, какая специальная семантика требуется атрибуту. Однако инструменты анализа кода, такие как FX / Cop, должны знать об этом.

Другой способ взглянуть на это: CIL определяет набор областей хранения: статическое (глобальное) хранилище, хранилище элементов и хранилище стека. TLS нет в этом списке, очень вероятно, потому что TLS не обязательно должен быть в этом списке. Если инструкций чтения и записи IL достаточно для доступа к TLS, когда символ помечен атрибутом TLS, почему IL должно иметь какое-либо специальное представление или обработку для TLS? Это не нужно.

Dthorpe
источник
Но разве это особое, зависящее от реализации поведение TLS полностью не подрывает «проверяемые» коммерческие преимущества .NET / CLR?
Дай
Я ничего не знаю о последствиях этого для дизайна, но считаю это очень полезным. Я уже где-то пользовался. Хотя я сомневаюсь, что это будет работать в веб-приложениях, использующих async / await, поскольку поток возобновления (после ожидания) может быть другим потоком. Я думал, что [ContextStatic]атрибут распространяется и на этот случай (его название подразумевает, что он работает на основе контекста потока), но, вероятно, я ошибался в своем предположении. Я вижу в сети людей, которые говорят, что [ContextStatic]атрибут используется с удаленным взаимодействием.
user2173353
Есть ли способ охватить все случаи (многопоточность, многопоточность веб-приложений с помощью async / await и т. Д.), Чтобы выполнить одну часть работы (например, один веб-запрос, обработку одного сообщения очереди и т.д.) получить свою версию переменной, не смотря ни на что?
user2173353
118

Как работает атрибут [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
11

[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();
}
Руи Руиво
источник
5

[ThreadStatic]Поля, отмеченные значком , создаются в локальном хранилище потока, поэтому у каждого потока есть собственная копия поля, т.е. область действия полей является локальной для потока.

Доступ к полям TLS осуществляется через регистры сегментов gs / fs. Эти сегменты используются ядрами ОС для доступа к памяти, зависящей от потока. Компилятор .net не испускает IL для заполнения / получения значения в TLS. Это делается ядром ОС.

Ариф Х-Шигри
источник