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



Является ли структура 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; }
}


- это свойство данных, потокобезопасным?

733   6  

6 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Нет, это не так. Я создал очень простое приложение, чтобы увидеть, получают ли 10/10 потоков производителя/потребителя доступ к одной и той же переменной структуры. И в конце концов вы увидите отладчик.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());

        }
    }
}

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

Comments

    Ничего не найдено.