Что делает метод потокобезопасным? Каковы правила?

Существуют ли общие правила/рекомендации для того, что делает метод потокобезопасным? Я понимаю, что, возможно, миллион разовых ситуаций, но что вообще? Это просто?

  • Если метод обрабатывает только локальные переменные, он безопасен для потоков.

Это так? Это относится и к статическим методам?

Один ответ, предоставленный @Cybis, был:

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

Это также относится к статическим методам?

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

Итак, я думаю, мой последний вопрос: "Есть ли короткий список правил, определяющих поточно-безопасный метод? Если да, то какие они?"

ИЗМЕНИТЬ
Здесь было много хороших моментов. Я думаю, что реальный ответ на этот вопрос: "Нет простых правил для обеспечения безопасности потоков". Круто. Хорошо. Но в целом я считаю, что принятый ответ дает хорошее краткое резюме. Всегда есть исключения. Быть по сему. Я могу жить с этим.

Ответ 1

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

В этом случае несколько потоков могут вызывать ThreadSafeMethod одновременно без проблем.

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number; // each thread will have its own variable for number.
        number = parameter1.Length;
        return number;
    }
}

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

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number;
        number = this.GetLength(parameter1);
        return number;
    }

    private int GetLength(string value)
    {
        int length = value.Length;
        return length;
    }
}

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

public class Thing
{
    private string someValue; // all threads will read and write to this same field value

    public int NonThreadSafeMethod(string parameter1)
    {
        this.someValue = parameter1;

        int number;

        // Since access to someValue is not synchronised by the class, a separate thread
        // could have changed its value between this thread setting its value at the start 
        // of the method and this line reading its value.
        number = this.someValue.Length;
        return number;
    }
}

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

Для обеспечения правильного concurrency вам необходимо использовать блокировку.

для получения дополнительной информации см. оператор блокировки Ссылка на С# и ReadWriterLockSlim.

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

Ответ 2

Если метод только обращается к локальным переменным, он потокобезопасен. Это оно?

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

fooobar.com/questions/53288/...

Это относится и к статическим методам?

Абсолютно нет.

Один ответ, предоставленный @Cybis, был: "Локальные переменные не могут быть разделены между потоками, потому что каждый поток получает свой собственный стек".

Абсолютно нет. Отличительной характеристикой локальной переменной является то, что она видна только внутри локальной области, а не в том, что она распределена во временном пуле. Это совершенно легально и возможно получить доступ к одной и той же локальной переменной из двух разных потоков.. Вы можете сделать это с помощью анонимных методов, lambdas, блоков итераторов или методов async.

Это также относится к статическим методам?

Абсолютно нет.

Если метод передан ссылочным объектом, это ли предотвращает безопасность потока?

Может быть.

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

Вам нужно научиться жить с разочарованием. Это очень сложный вопрос.

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

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

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

Снова посмотрим на мой пример: каждый метод тривиален. Именно так методы взаимодействуют друг с другом на "глобальном" уровне, что делает тупик программы. Вы не можете смотреть на каждый метод и проверять его как "безопасный", а затем ожидать, что вся программа будет безопасной, больше, чем вы можете сделать вывод, что, поскольку ваш дом состоит из 100% непустых кирпичей, что дом также не полый. Пустота дома является глобальным свойством всего, а не совокупностью свойств его частей.

Ответ 3

Нет жесткого и быстрого правила.

Вот некоторые правила для обеспечения безопасности кода в .NET и почему это не хорошие правила:

  • Функция и все функции, которые она вызывает, должны быть чистыми (без побочных эффектов) и использовать локальные переменные. Хотя это сделает ваш код потокобезопасным, также есть очень мало интересных вещей, которые вы можете сделать с этим ограничением в .NET.
  • Каждая функция, работающая на общем объекте, должна lock на обычную вещь. Все замки должны быть выполнены в том же порядке. Это сделает поток кода безопасным, но он будет невероятно медленным, и вы можете не использовать несколько потоков.
  • ...

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