Язык программирования C#9 и платформа .NET5
using System;using System.Threading;namespace MultiThreadedPrinting{public class Printer{public void PrintNumbers(){// Отобразить информацию о потоке.Console.WriteLine("-> {0} is executing PrintNumbers()",Thread.CurrentThread.Name);// Вывести числа.for (int i = 0; i < 10; i++){// Приостановить поток на случайный период времени.Random r = new Random();Thread.Sleep(1000 * r.Next(5));Console.Write("{0}, ", i);}Console.WriteLine();}}}Вызывающий код отвечает за создание массива из десяти (уникально именованных) объектов
, каждый из которых вызывает метод одного и того же экземпляра классаThread:Printerusing System;using System.Threading;using MultiThreadedPrinting;Console.WriteLine("*****Synchronizing Threads *****\n");Printer p = new Printer();// Создать 10 потоков, которые указывают на один.// и тот же метод того же самого объектаThread[] threads = new Thread[10];for (int i = 0; i < 10; i++){threads[i] = new Thread(new ThreadStart(p.PrintNumbers)){Name = $"Worker thread #{i}"};}// Теперь запустить их все.foreach (Thread t in threads){t.Start();}Console.ReadLine();Прежде чем взглянуть на тестовые запуски, кратко повторим суть проблемы. Первичный поток внутри этого домена приложения начинает свое существование с порождения десяти вторичных рабочих потоков. Каждому рабочему потоку указывается на необходимость вызова метода
того же самого экземпляра классаPrintNumbers(). Поскольку никаких мер для блокировки разделяемых ресурсов данного объекта (консоли) не предпринималось, есть неплохой шанс, что текущий поток будет вытеснен до того, как методPrinterвыведет полные результаты. Из-за того, что не известно в точности, когда подобное может произойти (если вообще произойдет), будут получены непредсказуемые результаты. Например, вывод может выглядеть так:PrintNumbers()*****Synchronizing Threads *****-> Worker thread #3 is executing PrintNumbers()-> Worker thread #0 is executing PrintNumbers()-> Worker thread #1 is executing PrintNumbers()-> Worker thread #2 is executing PrintNumbers()-> Worker thread #4 is executing PrintNumbers()-> Worker thread #5 is executing PrintNumbers()-> Worker thread #6 is executing PrintNumbers()-> Worker thread #7 is executing PrintNumbers()-> Worker thread #8 is executing PrintNumbers()-> Worker thread #9 is executing PrintNumbers()0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 2, 3, 1, 2, 2, 2, 1, 2,1, 1, 2, 2, 3, 3, 4,3, 3, 2, 2, 3, 4, 3, 4, 5, 4, 5, 4, 4, 3, 6, 7, 2, 3, 4, 4, 4, 5, 6, 5,3, 5, 8, 9,6, 7, 4, 5, 6, 6, 5, 5, 5, 8, 5, 6, 7, 8, 7, 7, 6, 6, 6, 8, 9,8, 7, 7, 7, 7, 9,6, 8, 9,8, 9,9, 9,8, 8, 7, 8, 9,9,9,Запустите приложение еще несколько раз. Скорее всего, каждый раз вы будете получать отличающийся вывод.
На заметку! Если получить непредсказуемый вывод не удается, увеличьте количество потоков с 10 до 100 (например) или добавьте в код еще один вызов
. В конце концов, вы столкнетесь с проблемой параллелизма.Thread.Sleep()Должно быть совершенно ясно, что здесь присутствуют проблемы. В то время как каждый поток сообщает экземпляру
о необходимости вывода числовых данных, планировщик потоков благополучно переключает потоки в фоновом режиме. В итоге получается несогласованный вывод. Нужен способ программной реализации синхронизированного доступа к разделяемым ресурсам. Как и можно было предположить, пространство именPrinterпредлагает несколько типов, связанных с синхронизацией. В языке C# также предусмотрено ключевое слово для синхронизации разделяемых данных в многопоточных приложениях.System.ThreadingСинхронизация с использованием ключевого слова lock языка C#
Первый прием, который можно применять для синхронизации доступа к разделяемым ресурсам, предполагает использование ключевого слова
языка С#. Оно позволяет определять блок операторов, которые должны быть синхронизованными между потоками. В результате входящие потоки не могут прерывать текущий поток, мешая ему завершить свою работу. Ключевое словоlockтребует указания маркера (объектной ссылки), который должен быть получен потоком для входа в область действия блокировки. Чтобы попытаться заблокировать закрытый метод уровня экземпляра, необходимо просто передать ссылку на текущий тип:lock