Добавить в цитаты Настройки чтения

Страница 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(&quot;тик &quot;) ; </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(&quot;так &quot;) ;</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>