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

У меня есть класс, подобный приведенному ниже:

class Program
    {
        static void Main(string[] args)
        {
            var outputWindow = new OutputWindow();

            var threads = new List<Thread>();

            Action action = () => outputWindow.Display(20);

            for (int i = 0; i < 10; i++)
            {
                var thread = new Thread(() => action()) {Name = "Thread " + i};
                threads.Add(thread);
            }

            foreach (var thread in threads)
            {
                thread.Start();
            }
        }
    }

    public class OutputWindow
    {
        public void Display(int x)
        {
            for (int i = 0; i < x; i++)
            {
                Console.WriteLine(Thread.CurrentThread.Name + " Outputcounter: " + i);
            }
        }
    }

Вопрос в том, является ли этот поток безопасным и приведет ли это к каким-либо условиям гонки для локальной переменной i внутри метода отображения? Будут ли все потоки увеличивать значение переменной «i», как и ожидалось (то есть оно увеличивает значение и не вводит в другие потоки значение i)

Если это потокобезопасно, будет ли безопасно предположить, что любые локальные переменные, объявленные в методе, всегда являются потокобезопасными, а общие переменные — это те, которые нуждаются в синхронизации?

Спасибо, -Майк


person Mike    schedule 17.01.2014    source источник
comment
У вас есть локальная переменная (не параметр) action (которая фиксирует в лямбде другую локальную переменную). Затем вы запускаете несколько потоков, давая каждому потоку новую лямбду, которая захватывает одну и ту же локальную переменную action. Эта переменная action действительно преобразуется в поле. Конечно, он не такой уж и локальный. Таким образом, эти локальные переменные не являются потокобезопасными. Когда вы спрашиваете о переменной i в своем коде, она действительно локальна. Он объявлен внутри метода, он не захватывается никакими лямбда-выражениями, не передается с ref или out, это неизменяемый тип. Так что проблем явно нет.   -  person Jeppe Stig Nielsen    schedule 17.01.2014
comment
Извините, я говорил о i в методе Display. i в методе Main записывается в лямбда-выражении. И это явно проблематично. i превращается в поле, которое встречается только в одном экземпляре. Вы, наверное, хотите это: for (int i = 0; i < 10; i++) { int copyOfI = i; var thread = new Thread(() => action()) {Name = "Thread " + copyOfI}; threads.Add(thread); }. Это было отмечено и в ответе Хенка. При этом copyOfI будет преобразовано в поле экземпляра сгенерированного класса, и будет десять экземпляров этого класса.   -  person Jeppe Stig Nielsen    schedule 17.01.2014
comment
Мой предыдущий комментарий был не совсем корректен, я думаю. Можно сказать new Thread(() => action()) /* ANONYMOUS LAMBDA HAS STOPPED HERE */ {Name = "Thread " + i /* Not capturing */ };.   -  person Jeppe Stig Nielsen    schedule 17.01.2014


Ответы (3)


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

public void AppendSomething(StringBuilder builder)
{
    builder.Append("Something");
}

Здесь builder по-прежнему является локальной переменной (параметры являются локальными переменными), и каждый вызов AppendSomething будет иметь независимую переменную, но вы можете вызывать метод из нескольких потоков, используя одну и ту же ссылку StringBuilder, так что этот метод не потокобезопасный. (Поскольку StringBuilder не является потокобезопасным.)

person Jon Skeet    schedule 17.01.2014

Пока ваши данные локальны, да, они потокобезопасны. Если он ссылается на данные вне этого метода, то это не так.

В вашем примере i не зависит от нескольких потоков (поскольку вы создаете переменную, устанавливаете локальное значение и просто увеличиваете значение).

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

person Kenneth    schedule 17.01.2014

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

Например, в вашем коде все потоки, которые вы создаете, имеют общий доступ к outputWindow и threads. Если объекты не являются потокобезопасными и вы вызываете их из нескольких потоков, у вас могут возникнуть проблемы. Например, если каждый поток попытается удалить себя из threads после завершения, у вас возникнет проблема, поскольку List<> не является потокобезопасным для чтения/записи.

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

person Sean    schedule 17.01.2014