Неизменна ли переменная потока безопасна в этом случае? - PullRequest
0 голосов
/ 27 октября 2018

Учитывая следующий пример кода, переменная i имеет неизменный тип int. После увеличения i новое значение сохраняется в той же ячейке памяти. Так что, если несколько потоков читают из этого места, некоторые из них могут получить поврежденные данные в тот момент, когда идет запись. Как тогда этот поток неизменного типа безопасен? Есть ли какая-то внутренняя логика CLR, которая позаботится об этом?

public class Test
{
    int i = 10;

    public unsafe int Run()
    {
        fixed (int* ip = &i)
        {
            Console.WriteLine($"address of i before updation: {((IntPtr)ip).ToString()}");
        }

        i = i + 1;

        fixed (int* ipNew = &i)
        {
            Console.WriteLine($"address of i after updation: {((IntPtr)ipNew).ToString()}");
        }

        return i;
    }
}

Обновление: Я обновил код, основываясь на комментариях, так как раньше мне было не ясно. А что если класс Test инициируется клиентом один раз, а метод Run вызывается несколькими потоками. Будет ли i считаться потокобезопасным?

1 Ответ

0 голосов
/ 27 октября 2018

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

Например, у вас есть три потока, AB и C. Что-то происходит, что заставляет B работать медленнее, чемдругие потоки.

A: Read i to thread local memory location A the value 10
B: Read i to thread local memory location B the value 10
A: Add 1 to thread local memory location A
A: Write 11 to i from thread local memory location A
B: Add 1 to thread local memory location B
C: Read i to thread local memory location C the value 11
C: Add 1 to thread local memory location C
C: Write 12 to i from thread local memory location C
B: Write 11 to i from thread local memory location B

Поскольку 3 операции, не являющиеся «атомарными», 3 потока могут выполнять работу между 3 шагами, которые должен был выполнить B, это приводит к неверному конечному значению.

Обычный способ справиться с этим - либо заблокировать 3 операции, чтобы только один поток мог сделать это одновременно,

lock(someObject)
{
   i = i + 1;
}

использовать инструмент, который делает операцию атомарной

Interlocked.Increment(ref i);

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

int iOriginal, iNew;
do 
{
    iOriginal = i;
    iNew = iOriginal + 1;
} while(iOriginal != Interlocked.CompareExchange(ref i, iNew, iOriginal)

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

Проще говоря, значение 10 является неизменным, переменная с именем i - нет.Если вы разделяете переменную (я говорю не об отношении объекта / значении, которое имеет переменная, а о самой переменной) между потоками, то вы работаете с изменяемым объектом.

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