приложение еще несколько раз. Скорее всего, каждый раз вы будете получать отличающийся вывод.
На заметку! Если получить непредсказуемый вывод не удается, увеличьте количество потоков с 10 до 100 (например) или добавьте в код еще один вызов
Thread.Sleep
. В конце концов, вы столкнетесь с проблемой параллелизма.
Должно быть совершенно ясно, что здесь присутствуют проблемы. В то время как каждый поток сообщает экземпляру
Printer
о необходимости вывода числовых данных, планировщик потоков благополучно переключает потоки в фоновом режиме. В итоге получается несогласованный вывод. Нужен способ программной реализации синхронизированного доступа к разделяемым ресурсам. Как и можно было предположить, пространство имен
System.Threading
предлагает несколько типов, связанных с синхронизацией. В языке C# также предусмотрено ключевое слово для синхронизации разделяемых данных в многопоточных приложениях.
Синхронизация с использованием ключевого слова lock языка C#
Первый прием, который можно применять для синхронизации доступа к разделяемым ресурсам, предполагает использование ключевого слова
lock
языка С#. Оно позволяет определять блок операторов, которые должны быть синхронизованными между потоками. В результате входящие потоки не могут прерывать текущий поток, мешая ему завершить свою работу. Ключевое слово
lock
требует указания маркера (объектной ссылки), который должен быть получен потоком для входа в область действия блокировки. Чтобы попытаться заблокировать закрытый метод уровня экземпляра, необходимо просто передать ссылку на текущий тип:
private void SomePrivateMethod
{
// Использовать текущий объект как маркер потока.
lock(this)
{
// Весь код внутри этого блока является безопасным к потокам.
}
}
Тем не менее, если блокируется область кода внутри открытого члена, то безопаснее (да и рекомендуется) объявить закрытую переменную-член типа
object
для применения в качестве маркера блокировки:
public class Printer
{
// Маркер блокировки.
private object threadLock = new object;
public void PrintNumbers
{
// Использовать маркер блокировки.
lock (threadLock)
{
...
}
}
}
В любом случае, если взглянуть
на метод
PrintNumbers
, то можно заметить, что разделяемым ресурсом, за доступ к которому соперничают потоки, является окно консоли. Поместите весь код взаимодействия с типом
Console
внутрь области
lock
, как показано ниже:
public void PrintNumbers
{
// Использовать в качестве маркера блокировки закрытый член object.
lock (threadLock)
{
// Вывести информацию о потоке.
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;
}
}
В итоге вы построили метод, который позволит текущему потоку завершить свою задачу. Как только поток входит в область
lock
, маркер блокировки (в данном случае ссылка на текущий объект) становится недоступным другим потокам до тех пор, пока блокировка не будет освобождена после выхода из области
lock
. Таким образом, если поток
А
получил маркер блокировки, то другие потоки не смогут войти ни в одну из областей, которые используют тот же самый маркер, до тех пор, пока поток
А
не освободит его.
На заметку! Если необходимо блокировать код в статическом методе, тогда следует просто объявить закрытую статическую переменную-член типа
object
, которая и будет служить маркером блокировки.
Запустив приложение, вы заметите, что каждый поток получил возможность выполнить свою работу до конца: