using System;
using System.Threading;
using System.Threading.Tasks;

namespace _1._41_Compare_and_Exchange_as_a_nonAtomic_operation
{
    public class Program
    {
        static int value = 1;

        public static void Main()
        {

                Task t1 = Task.Run(() =>
                {
                    if (value == 1)
                    {

                    Thread.Sleep(1000);

                        value = 2;

                    }
                });

                Task t2 = Task.Run(() =>
                {
                    value = 3;

                });

                Task.WaitAll(t1, t2);
                Console.WriteLine(value); //Displays 2

        }
    }
}

Я пытаюсь преобразовать приведенную выше неатомарную операцию в атомарную, используя :

Interlocked.CompareExhange(ref value, newValue, compareTo);

который я написал как

 Interlocked.CompareExhange(ref value, value, value); //This doesn't look right!

и

Interlocked.CompareExhange(ref value, t2, t1); //will not compile

Вопросы

  1. При наличии или отсутствии Interlocked.CompareExchange значение выводится как 2? Почему?
  2. Как правильно ссылаться на t1, t2?
  3. Почему я не могу ссылаться на вывод задачи напрямую? (значение, t2, t1)
  4. Необходимо ли какое-то преобразование или даже нужно ли оно вообще?
  5. Как я понимаю, значение должно быть обновлено до 2 (значение = 1), затем обновлено до 3 вместо текущего вывода (значение = 1, обновляется до 3, затем обновляется до 2) после использования Interlocked.CompareExchange?

Ответы (1)

Вместе с Interlocked.CompareExchange или без него значение выводится как 2? Почему?

На самом деле ответ таков: случайность. Ваш код недетерминирован, и его поведение зависит от планировщика потоков ОС.

Но... на самом деле совсем не удивительно, что t1 начинает выполняться раньше, чем t2. В этом случае проверка if (value == 1) выполняется до того, как t2 получит шанс выполнить value = 3;.

Подводя итог, самая вероятная хронология такова:

  • t1: значение сверяется с 1
  • t1: t1 переходит в спящий режим на одну секунду
  • t2: value установлено в 3
  • t1: просыпается на секунду позже
  • t1: value установлено на 2

Но, как я уже сказал выше, это именно то, что происходит на практике, но код тура остается недетерминированным, поскольку t2 может в принципе начать выполняться раньше t1.

Как правильно ссылаться на t1, t2?

То, как вы это делаете, кажется правильным, если я правильно понял ваш вопрос.

Почему я не могу напрямую ссылаться на вывод задачи? (value, t2, t1)

Вы запускаете void задачи, которые не имеют выхода. Они представлены типом Task.

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

var t = Task.Run(() => {
    // do anything
    return 42;
});

В этом случае t будет иметь тип Task, и вы сможете получить доступ к его свойству Result после завершения задачи (если вы попытаетесь получить доступ к i до завершения задачи, он будет заблокирован до завершения задачи).

Необходимо ли какое-то преобразование или даже необходимо?

Я не уверен, что понимаю ваш вопрос.

Как я понимаю, значение должно быть обновлено до 2 (значение =1), затем обновлено до 3 вместо текущего вывода (значение =1, обновляется до 3, затем обновляется до 2) после использования Interlocked.CompareExchange?

В основном, да. Interlocked.CompareExchange является атомарным. Он выполнит сравнение и установит значение за один шаг на аппаратном уровне:

Interlocked.CompareExchange(ref value, 2, 1);

Это атомный эквивалент:

if (value == 1)
    value = 2;

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

  • t1 выполняется перед t2:

    • t1: значение проверяется на 1, которое является истинным, поэтому значение устанавливается в 2
    • .
    • t2: value установлено в 3
    • .
  • t2 выполняется перед t1:

    • t2: value установлено в 3
    • t1: value проверяется на 1, что является ложным, поэтому value остается неизменным
    • .

Как видите, в обоих случаях в итоге получится 3.

2022 WebDevInsider