Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

-> Worker thread #5 is executing PrintNumbers()

Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

-> Worker thread #7 is executing PrintNumbers()

Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

-> Worker thread #6 is executing PrintNumbers()

Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

-> Worker thread #8 is executing PrintNumbers()

Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

-> Worker thread #9 is executing PrintNumbers()

Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

<p id="AutBody_Root551">Синхронизация с использованием типа System.Threading.Monitor</p>

Оператор lock языка C# на самом деле представляет собой сокращение для работы с классом System.Threading.Monitor. При обработке компилятором C# область lock преобразуется в следующую конструкцию (в чем легко убедиться с помощью утилиты ldasm.exe):

public void PrintNumbers()

{

  Monitor.Enter(threadLock);

  try

  {

    // Вывести информацию о потоке.

    Console.WriteLine("-> {0} is executing PrintNumbers()",

      Thread.CurrentThread.Name);

    // Вывести числа.

    Console.Write("Your numbers: ");

    for (int i = 0; i < 10; i++)

    {

      Random r = new Random();

      Thread.Sleep(1000 * r.Next(5));

      Console.Write("{0}, ", i);

    }

    Console.WriteLine();

  }

  finally

  {

    Monitor.Exit(threadLock);

  }

}

Первым делом обратите внимание, что конечным получателем маркера потока, который указывается как аргумент ключевого слова lock, является метод Monitor. Enter(). Весь код внутри области lock помещен внутрь блока try. Соответствующий блок finally гарантирует освобождение маркера блокировки (посредством метода Monitor.Exit()), даже если возникнут любые исключения времени выполнения. Модифицировав программу MultiThreadShareData с целью прямого применения типа Monitor (как только что было показано), вы обнаружите, что вывод идентичен.

С учетом того, что ключевое слово lock требует написания меньшего объема кода, чем при явной работе с типом System.Threading.Monitor, может возникнуть вопрос о преимуществах использования этого типа напрямую. Выражаясь кратко, тип Monitor обеспечивает большую степень контроля. Применяя тип Monitor, можно заставить активный поток ожидать в течение некоторого периода времени (с помощью статического метода Monitor.Wait()), информировать ожидающие потоки о том, что текущий поток завершен (через статические методы Monitor.Pulse() и Monitor.PulseAll()), и т.д.

Как и можно было ожидать, в значительном числе случаев ключевого слова lock будет достаточно. Если вас интересуют дополнительные члены класса Monitor, тогда обращайтесь в документацию по .NET Core.

<p id="AutBody_Root552">Синхронизация с использованием типа System.Threading.Interlocked</p>

Не заглядывая в код CIL, обычно нелегко поверить в то, что присваивание и простые арифметические операции не являются атомарными. По указанной причине в пространстве имен System.Threading предоставляется тип, который позволяет атомарно оперировать одиночным элементом данных с меньшими накладными расходами, чем тип Monitor. В классе Interlocked определены статические члены, часть которых описана в табл. 15.4.

Несмотря на то что это не сразу видно, процесс атомарного изменения одиночного значения довольно часто применяется в многопоточной среде. Пусть имеется код, который инкрементирует целочисленную переменную-член по имени intVal. Вместо написания кода синхронизации вроде показанного ниже:

int intVal = 5;

object myLockToken = new();

lock(myLockToken)

{

  intVal++;

}

код можно упростить, используя статический метод Interlocked.Increment().

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

Перейти на страницу:

Похожие книги