Подтвердить что ты не робот

Являются ли локальные переменные в статических методах потоками безопасными?

Если у меня есть статический класс со статическим методом, являются ли локальные переменные в методе безопасными, если на него называет несколько потоков?

static class MyClass {

  static int DoStuff(int n) {

    int x = n; // <--- Can this be modified by another thread?

    return x++;     
  }
}
4b9b3361

Ответ 1

Ответы на этот вопрос, в которых указано, что локали хранятся в стеке и поэтому являются потокобезопасными, являются неполными и, возможно, опасно неправильными.

Создают ли потоки собственную область при выполнении статических методов?

В вашем вопросе есть общая ошибка. "Сфера" в С# является чисто концепцией времени компиляции; "область" - это область текста программы, в которой конкретная сущность (такая как переменная или тип) может ссылаться на ее неквалифицированное имя. Области помогают определить, как компилятор сопоставляет имя с концепцией, которую представляет это имя.

Нити не создают "области" во время выполнения, потому что "scope" - это просто концепция времени компиляции.

Область локальной переменной связана - свободно - с ее временем жизни; грубо говоря, время жизни локальной переменной обычно начинается, когда поток управления вводит код, соответствующий началу его области, и заканчивается, когда поток управления уходит. Тем не менее, компилятору и среде выполнения предоставляется значительное усмотрение продлить или сократить срок службы, если они посчитают, что делать это эффективно или необходимо.

В частности, локали в блоках итератора и закрытые локали анонимных функций имеют время жизни, расширенное до точки, в которой управление выходит из области.

Однако ничто из этого не связано с безопасностью потоков. Поэтому давайте откажемся от этого плохо сформулированного вопроса и перейдем к несколько лучшей формулировке:

Если у меня есть статический класс со статическим методом, являются ли переменные экземпляра в методе безопасными, если вызывает его несколько потоков?

В вашем вопросе есть ошибка. Переменные экземпляра - это нестатические поля. Очевидно, что в статическом классе нет нестатических полей. Вы смешиваете переменные экземпляра с локальными переменными. Заданный вами вопрос:

Если у меня есть статический класс со статическим методом, являются ли локальные переменные в методе безопасными, если на него называет несколько потоков?

Вместо того, чтобы прямо ответить на этот вопрос, я перефразирую его на два вопроса, на которые легче ответить.

В каких обстоятельствах мне нужно использовать блокировку или другие специальные методы защиты потоков для обеспечения безопасного доступа к переменной?

Вам нужно сделать это, если есть два потока, оба могут получить доступ к переменной, по крайней мере один из потоков мутирует его, и по крайней мере один из потоков выполняет некоторую неатомную операцию на нем.

(Я отмечаю, что в игре могут быть и другие факторы. Например, если вам требуются постоянно наблюдаемые значения переменных общей памяти, как видно из каждого потока, тогда вам нужно использовать специальные методы, даже если все операции являются атомарными. С# не гарантирует последовательную согласованность наблюдений с общей памятью, даже если переменная отмечена как изменчивая.)

Super. Позвольте сосредоточиться на том, что "оба могут получить доступ к переменной". При каких обстоятельствах два потока могут обращаться к локальной переменной?

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

Однако существуют способы доступа к локальной переменной вне метода, который ее создал.

Во-первых, параметр "ref" или "out" может быть псевдонимом локальной переменной, но CLR и язык С# оба были тщательно разработаны таким образом, чтобы псевдонимы к локальным переменным были доступны только из методов, вызванных метод, объявляющий переменную (прямо или косвенно). Следовательно, это все равно должно быть поточным; не должно быть способа получить ссылку из одного потока в другой и тем самым обмениваться переменной по потокам.

Во-вторых, локальная переменная может быть закрытой локалью лямбда или анонимного метода. В этом случае локальная переменная не обязательно является потокобезопасной. Если делегат хранится в общей памяти, тогда два потока могут самостоятельно управлять локальным!

static class Foo
{
    private static Func<int> f;
    public static int Bar()
    {
        if (Foo.f == null)
        {
           int x = 0;
           Foo.f = ()=>{ return x++; };
        }
        return Foo.f();
    }
}

Здесь "Bar" имеет локальную переменную "x". Если Bar вызывается в нескольких потоках, тогда сначала раскладываются потоки, чтобы определить, кто устанавливает Foo.f. Один из них побеждает. И с этого момента, вызовы в Bar по нескольким потокам все ненавязчиво управляют одной и той же локальной переменной x, которая была захвачена делегатом, созданным побеждающим потоком. Локальная переменная не является гарантией безопасности потоков.

В-третьих, локальная переменная внутри блока итератора имеет одинаковую проблему:

static class Foo
{
    public static IEnumerable<int> f;
    private static IEnumerable<int> Sequence()
    {
        int x = 0;
        while(true) yield return x++;
    }
    public static Bar() { Foo.f = Sequence(); }
}

Если кто-то вызывает Foo.Bar(), а затем обращается к Foo.f из двух разных потоков, снова одна локальная переменная x может быть ненавязчиво мутирована на двух разных потоках. (И, конечно, механизмы, которые запускают логику итератора, также не являются потокобезопасными.)

В-четвертых, в коде, помеченном как "небезопасное", локальная переменная может быть разделена между потоками путем совместного использования указателя на локальный поток. Если вы отметите блок кода как "небезопасный", тогда вы несете ответственность за то, что код является потокобезопасным, если это необходимо. Не выключайте систему безопасности, если вы не знаете, что делаете.