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

Безопасность потоков в классе String

Можно ли потокобезопасно создавать строки из локальных переменных, используя класс String, как в методах ниже? Предположим, что приведенные ниже методы вызывают из нескольких потоков.

public static string WriteResult(int value, string name)
{
    return string.Format("Result: value={0} name={1}", value, name);
}

public static string WriteResult2(int value, string name)
{
    return "Result: value=" + value + " name=" + name;
}

Или мне нужно использовать StringBuilder для обеспечения безопасности потоков?

4b9b3361

Ответ 1

Это абсолютно нормально. Не существует общего состояния ни в одном фрагменте кода, кроме строковых литералов. Поскольку строки являются неизменными, отлично, чтобы строки были свободно распределены между потоками, а string.Format и string.Concat (неявно вызываемые во второй части кода) являются потокобезопасными.

Даже если один из параметров был изменен и даже если этот метод мутировал параметры, например

public static void AddResult(int value, List<string> results)
{
    results.Add("Value " + value);
}

... тогда сам метод по-прежнему будет потокобезопасным, если несколько потоков не относятся к одному и тому же List<string>. Если несколько потоков ссылались на один и тот же List<string>, тогда это было бы небезопасно, даже если бы оно просто прочитало из списка, поскольку другой поток мог бы его мутировать.

Ответ 2

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

Поэтому нет необходимости заботиться о безопасности потоков с помощью Format или String.Concat в этом случае.


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

public class MyClass
{
    public Int32 value1 { get; set; }
    public String value2 { get; set;}
} 

public static string WriteResult2(MyObject obj)
{
    return "Result: value=" + obj.value1 + " name=" + obj.value2 ;
}

В этом случае, с первым или вторым подходом вы можете вернуть несогласованное значение (это значение value1 и value2 изменяются после того, как одно значение уже отправлено на вывод.)

Как @JonSkeet указал, на самом деле этот метод небезопасен, но сам класс небезопасен для совместного использования между различными потоками.

Чтобы справиться с этой ситуацией, вам понадобится либо создать специальный метод проверки потока:

public class MyClass
{
    private Object lockObj = new Object();
    public Int32 value1
    {
        get
        {
            lock (this.lockObj) { ... });
        }
        set
        {
            lock (this.lockObj) { ... });
        }
    }
    public String value2
    {
        get
        {
            lock (this.lockObj) { ... });
        }
        set
        {
            lock (this.lockObj) { ... });
        }
    }

    public string WriteResult2()
    {
        lock (this.lockObj)
        {
             return "Result: value=" + this.value1 + " name=" + this.value2 ;
        }
    }
} 

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

Ответ 3

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

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

  • value не может быть изменен, поскольку он имеет примитивный тип и передается значением
  • name нельзя изменить, поскольку объекты string неизменяемы.