После этой подстановки результат выполнения данной программы будет выглядеть следующим образом.

Тик тик тик тик тик так

Так

Так

Так

Так

Часы остановлены

Очевидно, что методы Tick ( ) и Tock ( ) больше не синхронизированы!

Взаимоблокировка и состояние гонки

При разработке многопоточных программ следует быть особенно внимательным, чтобы избежать взаимоблокировки и состояний гонок. Взаимоблокировка, как подразумевает само название, — это ситуация, в которой один поток ожидает определенных действий от другого потока, а другой поток, в свою очередь, ожидает чего-то от первого потока. В итоге оба потока приостанавливаются, ожидая друг друга, и ни один из них не выполняется. Эта ситуация напоминает двух слишком вежливых людей, каждый из которых настаивает на том, чтобы другой прошел в дверь первым!

На первый взгляд избежать взаимоблокировки нетрудно, но на самом деле не все так просто, ведь взаимоблокировка может возникать окольными путями. В качестве примера рассмотрим класс TickTock из предыдущей программы. Как пояснялось выше, в отсутствие завершающего вызова метода Pulse () из метода Tick () или Tock () тот или другой будет ожидать до бесконечности, что приведет к "зависанию" программы вследствие взаимоблокировки. Зачастую причину взаимоблокировки не так-то просто выяснить, анализируя исходный код программы, поскольку параллельно действующие процессы могут взаимодействовать довольно сложным образом во время выполнения. Для исключения взаимоблокировки требуется внимательное программирование и тщательное тестирование. В целом, если многопоточная программа периодически "зависает", то наиболее вероятной причиной этого является взаимоблокировка.

Состояние гонки возникает в том случае, когда два потока или больше пытаются одновременно получить доступ к общему ресурсу без должной синхронизации. Так, в одном потоке может сохраняться значение в переменной, а в другом — инкрементироваться текущее значение этой же переменной. В отсутствие синхронизации конечный результат будет зависеть от того, в каком именно порядке выполняются потоки: инкрементируется ли значение переменной во втором потоке или же оно сохраняется в первом. О подобной ситуации говорят, что потоки "гоняются друг за другом", причем конечный результат зависит от того, какой из потоков завершится первым. Возникающее состояние гонок, как и взаимоблокировку, непросто обнаружить. Поэтому его лучше предотвратить, синхронизируя должным образом доступ к общим ресурсам при программировании.

Применение атрибута MethodlmplAttribute

Метод может быть полностью синхронизирован с помощью атрибута MethodlmplAttribute. Такой подход может стать альтернативой оператору lock в тех случаях, когда метод требуется заблокировать полностью. Атрибут

MethodlmplAttгibute определен в пространстве имен System . Runtime . CompilerServices. Ниже приведен конструктор, применяемый для подобной синхронизации:

public MethodlmplAttribute(MethodlmplOptions methodlmplOptions)

где methodlmplOptions обозначает атрибут реализации. Для синхронизации метода достаточно указать атрибут MethodlmplOptions. Synchronized. Этот атрибут вызывает блокировку всего метода для текущего экземпляра объекта, доступного по ссылке this. Если же метод относится к типу static, то блокируется его тип. Поэтому данный атрибут непригоден для применения в открытых объектах или классах.

Ниже приведена еще одна версия программы, имитирующей тиканье часов, с переделанным вариантом класса TickTock, в котором атрибут MethodlmplOptions обеспечивает должную синхронизацию.

// Использовать атрибут MethodlmplAttribute для синхронизации метода.

Using System;

Using System.Threading;

Наши рекомендации