Страница 409 из 502
<b>class TickTock { </b>
<b> object lockOn = new object(); </b>
<b> public void Tick(bool ru
<b> lock(lockOn) { </b>
<b> if (!ru
<b> return; </b>
<b> } </b>
<b> Console.Write("тик ") ; </b>
<b> } </b>
<b> } </b>
<b> public void Tock (bool ru
<b> lock(lockOn) { </b>
<b> if(!ru
<b> return; </b>
<b> } </b>
<b> Console.Write("так ") ;</b>
<b> }</b>
<b> }</b>
<b>}</b>
После этой подстановки результат выполнения данной программы будет выглядеть следующим образом.
<b>тик так так так так так тик тик тик тик Часы остановлены</b>
Очевидно, что методы Tick() и Tock() больше не синхронизированы!
Взаимоблокировка и состояние гонки
При разработке многопоточных программ следует быть особенно внимательным, чтобы избежать взаимоблокировки и состояний гонок. Взаимоблокировка, как подразумевает само название, — это ситуация, в которой один поток ожидает определенных действий от другого потока, а другой поток, в свою очередь, ожидает чего-то от первого потока. В итоге оба потока приостанавливаются, ожидая друг друга, и ни один из них не выполняется. Эта ситуация напоминает двух слишком вежливых людей, каждый из которых настаивает на том, чтобы другой прошел в дверь первым!
На первый взгляд избежать взаимоблокировки нетрудно, но на самом деле не все так просто, ведь взаимоблокировка может возникать окольными путями. В качестве примера рассмотрим класс TickTock из предыдущей программы. Как пояснялось выше, в отсутствие завершающего вызова метода Pulse() из метода Tick() или Tock() тот или другой будет ожидать до бесконечности, что приведет к "зависанию" программы вследствие взаимоблокировки. Зачастую причину взаимоблокировки не так-то просто выяснить, анализируя исходный код программы, поскольку параллельно действующие процессы могут взаимодействовать довольно сложным образом во время выполнения. Для исключения взаимоблокировки требуется внимательное программирование и тщательное тестирование. В целом, если многопоточная программа периодически "зависает", то наиболее вероятной причиной этого является взаимоблокировка.
Состояние гонки возникает в том случае, когда два потока или больше пытаются одновременно получить доступ к общему ресурсу без должной синхронизации. Так, в одном потоке может сохраняться значение в переменной, а в другом — инкрементироваться текущее значение этой же переменной. В отсутствие синхронизации конечный результат будет зависеть от того, в каком именно порядке выполняются потоки: инкрементируется ли значение переменной во втором потоке или же оно сохраняется в первом. О подобной ситуации говорят, что потоки "гоняются друг за другом", причем конечный результат зависит от того, какой из потоков завершится первым. Возникающее состояние гонок, как и взаимоблокировку, непросто обнаружить. Поэтому его лучше предотвратить, синхронизируя должным образом доступ к общим ресурсам при программировании.
Применение атрибута MethodlmplAttribute
Метод может быть полностью синхронизирован с помощью атрибута MethodlmplAttribute. Такой подход может стать альтернативой оператору lock в тех случаях, когда метод требуется заблокировать полностью. Атрибут MethodlmplAttгibute определен в пространстве имен System.Runtime.CompilerServices. Ниже приведен конструктор, применяемый для подобной синхронизации:
<b>public MethodlmplAttribute(MethodlmplOptions methodlmplOptions)</b>
где methodlmplOptions обозначает атрибут реализации. Для синхронизации метода достаточно указать атрибут MethodlmplOptions.Synchronized. Этот атрибут вызывает блокировку всего метода для текущего экземпляра объекта, доступного по ссылке this. Если же метод относится к типу static, то блокируется его тип. Поэтому данный атрибут непригоден для применения в открытых объектах или классах.
Ниже приведена еще одна версия программы, имитирующей тиканье часов, с переделанным вариантом класса TickTock, в котором атрибут MethodlmplOptions обеспечивает должную синхронизацию.
<b>// Использовать атрибут MethodlmplAttribute</b>
<b>// для синхронизации метода.</b>
<b>using System;</b>
<b>using System.Threading;</b>
<b>using System.Runtime.CompilerServices;</b>
<b>//Вариант класса TickTock, переделанный с целью</b>
<b>// использовать атрибут MethodlmplOptions.Synchronized,</b>
<b>class TickTock {</b>
<b> /* Следующий атрибут полностью синхронизирует метод Tick(). */</b>
<b> [MethodImplAttribute(MethodImplOptions.Synchronized)]</b>
<b> public void Tick(bool ru
<b> if (!ru
<b> Monitor.Pulse(this); // уведомить любые ожидающие потоки</b>
<b> return;</b>
<b> }</b>