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

Является ли структура C# потокобезопасной?

Например, если есть:

struct Data
{
    int _number;
    public int Number { get { return _number; } set { _number = value; } }

    public Data(int number) { _number = number; }
}

в другом типе:

class DadData
{
    public Data TheData { get; set; }
}

Является ли свойство с именем TheData поточно-ориентированным?


person Kaveh Shahbazian    schedule 28.02.2010    source источник
comment
Это зависит. Что вы делаете со структурой?   -  person SLaks    schedule 01.03.2010
comment
Кроме того, вы не должны создавать изменяемую структуру. stackoverflow.com/questions/441309/why-are-mutable -structs-evil   -  person SLaks    schedule 01.03.2010
comment
В соответствии с этим stackoverflow.com/questions/ 2353014/are-c-structs-thread-safe/ следует.   -  person Fitzchak Yitzchaki    schedule 01.03.2010


Ответы (6)


Нет, структуры в .NET не являются потокобезопасными по своей сути.

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

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

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

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

person casperOne    schedule 28.02.2010
comment
И что еще хуже, это также очень зависит от того, взаимодействует ли ваш код с полем, переменной или свойством. . К счастью, в этом случае автоматически реализуемое свойство (TheData) удаляет большинство из них. Так что я просто упоминаю об этом для полноты картины ;-p - person Marc Gravell; 01.03.2010
comment
Спасибо casperOne! Спасибо, Марк! Как выразился casperOne, я подумал, что семантика копирования по значению имеет большое значение для этого разговора; пока не удалось увидеть какие-либо инструкции по ссылкам. Этот код не мой фактический код, а представление. Я пишу несколько многопоточных приложений; и некоторые распространенные инструменты параллельного программирования — C#; потому что некоторые паттерны часто возвращаются ;) (Конечно, я поищу здесь критику, когда у меня получится что-то стоящее). - person Kaveh Shahbazian; 01.03.2010

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

struct Data
{
    readonly int _number;
    public int Number { get { return _number; } }

    public Data(int number) { _number = number; }
}

тогда да; это потокобезопасно. Во всех остальных случаях ответ «вероятно, нет».

Также обратите внимание, что применяются правила атомарности, поэтому даже единичное чтение или обновление DadData.TheData нельзя считать потокобезопасным, даже с неизменяемой структурой. Вы можете (особенно для негабаритных структур) иметь один поток, читающий структуру, в то время как другой поток перезаписывает ее; без синхронизации будут происходить плохие вещи (в конце концов).

person Marc Gravell    schedule 28.02.2010

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

Кроме того, изменяемые структуры — это запахи кода. Есть ли какая-то особая причина, по которой вам нужно, чтобы это было struct вместо class? Нужна ли вам семантика типа значения для этих данных?

person Aaronaught    schedule 28.02.2010

Прямые операции чтения и записи различных элементов изменяемой структуры разными потоками не будут мешать друг другу. Доступ разных потоков к одному и тому же члену через методы Interlocked будет вести себя в соответствии с семантикой этих методов. Эти факты могут позволить изменяемым структурам обеспечить потокобезопасное поведение.

Изменяемые области хранения, содержащие структуры, которые не предлагают никаких средств изменения, кроме прямой замены, не обеспечивают никакой безопасности потоков, за исключением случаев, когда структура содержит либо одно 32-битное целое число, либо одну ссылку на объект, попытка чтения такого ( одноэлементное) место хранения структуры в то же время, когда оно записывается, гарантированно считывает полностью старые данные или полностью новые данные. Обратите внимание, что невозможно использовать любой из методов Interlocked с неизменяемыми структурами — даже со структурами, которые содержат только одно целое число или ссылку на объект.

person supercat    schedule 07.02.2012

Нет, они не. Я создал очень простое приложение, чтобы увидеть, обращаются ли 10/10 потоков производителей/потребителей к одной и той же структурной переменной. И в конце концов вы увидите Debugger.Break(); будет поражен. Баланс банка никогда не должен опускаться ниже 0.

namespace StructThreadSafe
{
    class Program
    {
        struct BankBalance
        {
            public decimal Balance { get; set; }
        }

        static void Main(string[] args)
        {
            BankBalance bankBalance = new BankBalance();
            bankBalance.Balance = 100;
            List<Task> allTasks = new List<Task>();
            for (int q = 0; q < 10; q++)
            {
                Task producer = new Task(() =>
                {
                    for (int i = 0; i < 1000; i++)
                    {
                        if (bankBalance.Balance < 0)
                        {
                            if (Debugger.IsAttached)
                            {
                                Debugger.Break();
                            }   
                        }
                        bankBalance.Balance += 5;
                        Console.WriteLine("++Current Balance: " + bankBalance.Balance);
                        System.Threading.Thread.Sleep(100);
                    }
                });
                allTasks.Add(producer);
            }
            for (int w = 0; w < 10; w++)
            {
                Task consumer = new Task(() =>
                {
                    for (int i = 0; i < 1000; i++)
                    {
                        if (bankBalance.Balance < 0)
                        {
                            if (Debugger.IsAttached)
                            {
                                Debugger.Break();
                            }
                        }
                        if (bankBalance.Balance > 15)
                        {
                            bankBalance.Balance -= 15;
                            Console.WriteLine("--Current Balance: " + bankBalance.Balance);
                        }
                        else
                        {
                            Console.WriteLine("**Current Balance below minimum: " + bankBalance.Balance);
                        }
                        System.Threading.Thread.Sleep(100);
                    }
                });
                allTasks.Add(consumer);
            }
            allTasks.ForEach(p => p.Start());
            Task.WaitAll(allTasks.ToArray());

        }
    }
}
person Teoman shipahi    schedule 15.05.2015

Нет. Почему это должно быть потокобезопасным? Это просто данные. Он не становится поточно-ориентированным по волшебству.

person John Saunders    schedule 28.02.2010