Как упоминалось ранее, каждый поток имеет свои собственные ресурсы, но область кода является общей, то есть каждый поток может выполнять одну и ту же функцию. Проблема, которую это может вызвать, заключается в том, что несколько потоков одновременно выполняют функцию, что приводит к путанице данных и непредсказуемым результатам, поэтому мы должны избегать такой ситуации.
В C# предусмотрена блокировка по ключевому слову, которая может определить раздел кода как взаимоисключающий раздел (критический раздел). Взаимоисключающий раздел позволяет одновременно приступить к выполнению только одному потоку, в то время как другие потоки должны ждать. В C# блокировка ключевого слова определяется следующим образом:
блокировка (выражение) оператор_блок
выражение представляет объект, который вы хотите отслеживать, обычно это ссылка на объект.
Оператор_блок — это код взаимоисключающего раздела. Этот код может выполняться только одним потоком одновременно.
Ниже приведен типичный пример использования ключевого слова lock. Использование и назначение ключевого слова lock объяснены в комментариях.
Примеры следующие:
Код
использование системы;
использование System.Threading;
пространство имен ThreadSimple
{
Внутренняя учетная запись класса
{
внутренний баланс;
Случайный r = новый Random();
внутренний счет (int начальный)
{
баланс = начальный;
}
внутренний int Вывод(целое количество)
{
если (баланс < 0)
{
//Если баланс меньше 0, выдаем исключение
выдать новое исключение («Отрицательный баланс»);
}
//Следующий код гарантированно завершится до того, как текущий поток изменит значение баланса.
//Ни один другой поток не выполнит этот код для изменения значения баланса
//Поэтому значение баланса не может быть меньше 0
заблокировать (это)
{
Console.WriteLine("Текущая тема:"+Thread.CurrentThread.Name);
//Если ключевое слово блокировки не защищено, это может произойти после выполнения условного решения if.
//Другой поток выполнил баланс=баланс-сумма и изменил значение баланса.
//Эта модификация невидима для этого потока, поэтому может привести к тому, что условие if перестанет выполняться.
//Однако этот поток продолжает выполнять баланс=баланс-сумма, поэтому баланс может быть меньше 0
если (баланс >= сумма)
{
Thread.Sleep(5);
баланс = баланс - сумма;
сумма возврата;
}
еще
{
вернуть 0 // транзакция отклонена;
}
}
}
внутренняя пустота DoTransactions()
{
для (int я = 0; я <100; я++)
Вывести(r.Next(-50, 100));
}
}
внутренний тест класса
{
статические внутренние потоки Thread[] = новый Thread[10];
общественная статическая пустота Main()
{
Аккаунт аккаунта = новый аккаунт (0);
для (int я = 0; я <10; я++)
{
Поток t = новый поток (новый ThreadStart (acc.DoTransactions));
потоки [я] = т;
}
для (int я = 0; я <10; я++)
потоки[i].Name=i.ToString();
для (int я = 0; я <10; я++)
потоки[i].Start();
Консоль.ReadLine();
}
}
}Класс монитора блокирует объект
Когда несколько потоков совместно используют объект, также возникают проблемы, аналогичные общим коду. Для таких проблем не следует использовать ключевое слово lock. Здесь необходимо использовать класс Monitor в System.Threading. Мы можем назвать это монитором. , Monitor предоставляет решение, позволяющее потокам совместно использовать ресурсы.
Класс Monitor может заблокировать объект, а поток может работать с объектом только в том случае, если он получил блокировку. Механизм блокировки объекта гарантирует, что только один поток может получить доступ к этому объекту одновременно в ситуациях, которые могут вызвать хаос. Монитор должен быть связан с определенным объектом, но поскольку это статический класс, его нельзя использовать для определения объектов, а все его методы являются статическими, и на них нельзя ссылаться с помощью объектов. Следующий код иллюстрирует использование Monitor для блокировки объекта:
...
Очередь oQueue=новая очередь();
...
Monitor.Enter(oQueue);
......//Теперь объектом oQueue может управлять только текущий поток
Monitor.Exit(oQueue);//Снимаем блокировку
Как показано выше, когда поток вызывает метод Monitor.Enter() для блокировки объекта, объект принадлежит ему. Если другие потоки хотят получить доступ к этому объекту, им остается только дождаться, пока он воспользуется Monitor.Exit(). метод снятия блокировки. Чтобы гарантировать, что поток в конечном итоге снимет блокировку, вы можете написать метод Monitor.Exit() в блоке кодаfinally структуры try-catch-finally.
Для любого объекта, заблокированного Монитором, некоторая информация, связанная с ним, сохраняется в памяти:
Один из них — это ссылка на поток, который в данный момент удерживает блокировку;
Вторая — это очередь подготовки, в которой хранятся потоки, готовые получить блокировки;
Третий — очередь ожидания, содержащая ссылку на очередь, ожидающую изменения статуса объекта в данный момент.
Когда поток, владеющий блокировкой объекта, готов снять блокировку, он использует метод Monitor.Pulse() для уведомления первого потока в очереди ожидания, поэтому поток переводится в очередь подготовки, когда блокировка объекта снимается. , в очереди подготовки. Поток может немедленно получить блокировку объекта.
Ниже приведен пример, показывающий, как использовать ключевое слово lock и класс Monitor для реализации синхронизации потоков и связи. Это также типичная проблема производителя и потребителя.
В этой процедуре поток-производитель и поток-потребитель чередуются. Производитель записывает число, а потребитель сразу его читает и отображает (суть программы изложена в комментариях).
Используемые системные пространства имен следующие:
использование системы;
использование System.Threading;
Сначала определите класс Cell объекта, над которым выполняется операция. В этом классе есть два метода: ReadFromCell() и WriteToCell. Поток-потребитель вызовет ReadFromCell(), чтобы прочитать содержимое cellContents и отобразить его, а процесс-производитель вызовет метод WriteToCell() для записи данных в cellContents.
Примеры следующие:
Код
ячейка общественного класса
{
int cellContents; //Содержимое объекта Cell
bool readFlag = false; // Флаг состояния, если он истинен, его можно читать, если ложь, то записывается.
public int ReadFromCell( )
{
lock(this) // Что гарантирует ключевое слово Lock. Прочтите предыдущее введение в lock.
{
if (!readerFlag)//Если сейчас он не читается
{
пытаться
{
//Дождитесь вызова метода Monitor.Pulse() в методе WriteToCell
Monitor.Wait(это);
}
улов (SynchronizationLockException e)
{
Console.WriteLine(e);
}
улов (ThreadInterruptedException e)
{
Console.WriteLine(e);
}
}
Console.WriteLine("Consume: {0}",cellContents);
ReaderFlag = ложь;
//Сбрасываем флаг readerFlag, чтобы указать, что поведение потребления завершено
Monitor.Pulse(это);
//Уведомляем метод WriteToCell() (этот метод выполняется в другом потоке и ожидает)
}
вернуть содержимое ячейки;
}
общественная недействительность WriteToCell (int n)
{
заблокировать (это)
{
если (читательфлаг)
{
пытаться
{
Monitor.Wait(это);
}
улов (SynchronizationLockException e)
{
//При вызове синхронизированных методов (имеются в виду методы класса Monitor, кроме Enter) в асинхронных областях кода
Console.WriteLine(e);
}
улов (ThreadInterruptedException e)
{
//Прерываем, когда поток находится в состоянии ожидания
Console.WriteLine(e);
}
}
CellContents = п;
Console.WriteLine("Produce: {0}",cellContents);
ReaderFlag = Истина;
Monitor.Pulse(это);
//Уведомляем ожидающий метод ReadFromCell() в другом потоке
}
}
}
Класс-производитель CellProd и класс-потребитель CellCons определены ниже. Оба они имеют только один метод ThreadRun(), поэтому прокси-объект ThreadStart, предоставляемый потоку в функции Main(), служит входом в поток.
общедоступный класс CellProd
{
Cell cell // Объект ячейки, над которым выполняется операция;
int Quantity = 1 // Количество раз, когда производитель производит, инициализируется значением 1;
public CellProd (Ячейка ячейки, запрос int)
{
//Конструктор
ячейка = коробка;
количество = запрос;
}
публичная пустота ThreadRun( )
{
for(intloer=1;loer<=количество;loer++)
cell.WriteToCell(looper); //Производитель записывает информацию в объект операции
}
}
общедоступный класс CellCons
{
Клеточная клетка;
целое количество = 1;
public CellCons (Ячейка ячейки, запрос int)
{
//Конструктор
ячейка = коробка;
количество = запрос;
}
публичная пустота ThreadRun( )
{
интервал valReturned;
for(intloer=1;loer<=количество;loer++)
valReturned=cell.ReadFromCell();//Потребитель считывает информацию из объекта операции
}
}
Затем в функции Main() класса MonitorSample, приведенной ниже, нам нужно создать два потока в качестве производителей и потребителей и использовать методы CellProd.ThreadRun() и CellCons.ThreadRun() для выполнения операций над одним и тем же потоком. Объект ячейки.
Код
общедоступный класс MonitorSample
{
public static void Main(String[] args)
{
int result = 0; // Бит флага. Если он равен 0, это означает, что в программе нет ошибки. Если он равен 1, это означает, что ошибка есть.
Ячейка ячейка = новая ячейка( );
//Далее используется ячейка для инициализации классов CellProd и CellCons. Время производства и потребления равно 20.
CellProd prod = новый CellProd(ячейка, 20);
CellCons cons = new CellCons(ячейка, 20);
Производитель потока = новый поток (новый ThreadStart (prod.ThreadRun));
Потребитель потока = новый поток (новый ThreadStart (cons.ThreadRun));
//Поток-производитель и поток-потребитель созданы, но выполнение еще не началось.
пытаться
{
продюсер.Старт( );
потребитель.Старт( );
продюсер.Присоединиться( );
потребитель.Присоединиться( );
Консоль.ReadLine();
}
улов (ThreadStateException e)
{
//Когда поток не может выполнить запрошенную операцию из-за своего состояния
Console.WriteLine(e);
результат = 1;
}
улов (ThreadInterruptedException e)
{
//Прерываем, когда поток находится в состоянии ожидания
Console.WriteLine(e);
результат = 1;
}
//Хотя функция Main() не возвращает значение, следующий оператор может вернуть результат выполнения родительскому процессу
Environment.ExitCode = результат;
}
}
В приведенной выше процедуре синхронизация выполняется путем ожидания Monitor.Pulse(). Сначала производитель производит ценность, а потребитель в то же время находится в состоянии ожидания, пока не получит «Пульс» от производителя, уведомляющий его о завершении производства. После этого потребитель переходит в состояние потребления. и производитель начинает ждать завершения работы потребителя. «Импульс», излучаемый Monitor.Pulese(), будет вызван после операции.
Результат его выполнения прост:
Продукция: 1
Потребление: 1
Продукция: 2
Потребление: 2
Продукция: 3
Потребление: 3
...
...
Продукция: 20
Потребление: 20
Фактически, этот простой пример помог нам решить большие проблемы, которые могут возникнуть в многопоточных приложениях. Если вы понимаете основные методы разрешения конфликтов между потоками, их легко применить к более сложным программам.
-