Иллюстрированный самоучитель по Visual Basic .NET

Взаимная блокировка

Центральное место в классе Fork занимает метод "захвата вилки" GrabFork, определяемый в строках 25-27.

Строки 26 и 27 просто выводят на консоль отладочную информацию. В основном коде метода (строки 28-36) доступ к вилке синхронизируется по объектной переменной Me. Поскольку в нашей программе используется только одна вилка, синхронизация по Me гарантирует, что два потока не смогут одновременно захватить ее. Команда Sleep (в блоке, начинающемся в строке 34) имитирует задержку между захватом вилки/ножа и началом еды. Учтите, что команда Sleep не снимает блокировку с объектов и лишь ускоряет возникновение взаимной блокировки!

Однако наибольший интерес представляет код класса Programmer (строки 61-108). В строках 67-70 определяется общий конструктор, что гарантирует наличие в программе только одной вилки и ножа. Код свойств (строки 74-94) прост и не требует комментариев. Самое главное происходит в методе Eat, выполняемом двумя отдельными потоками. Процесс продолжается в цикле до тех пор, пока какой-либо поток не захватит вилку вместе с ножом.

В строках 98-102 объект случайным образом захватывает вилку/нож, используя вызов Rnd, – именно это и порождает взаимную блокировку. Происходит следующее:

  1. Поток, выполняющий метод Eat объекта Тот, активизируется и входит в цикл. Он захватывает нож и переходит в состояние ожидания.
  2. Поток, выполняющий метод Eat объекта Bob, активизируется и входит в цикл. Он не может захватить нож, но захватывает вилку и переходит в состояние ожидания.
  3. Поток, выполняющий метод Eat объекта Тот, активизируется и входит в цикл. Он пытается захватить вилку, однако вилка уже захвачена объектом Bob; поток переходит в состояние ожидания.
  4. Поток, выполняющий метод Eat объекта Bob, активизируется и входит в цикл. Он пытается захватить нож, однако нож уже захвачен объектом Тот; поток переходит в состояние ожидания.

Все это продолжается до бесконечности – перед нами типичная ситуация взаимной блокировки (попробуйте запустить программу, и вы убедитесь в том, что поесть так никому и не удается).

О возникновении взаимной блокировки можно узнать и в окне потоков. Запустите программу и прервите ее клавишами CTRL + Break. Включите в окно просмотра переменную Me и откройте окно потоков. Результат выглядит примерно так, как показано на рис. 10.7. Из рисунка видно, что поток Bob захватил нож, но вилки у него нет. Щелкните правой кнопкой мыши в окне потоков на строке Тот и выберите в контекстном меню команду Switch to Thread. Окно просмотра показывает, что у потока Тот имеется вилка, но нет ножа. Конечно, это не является стопроцентным доказательством, но подобное поведение по крайней мере заставляет заподозрить неладное.

Примечание
Если вариант с синхронизацией по одному объекту (как в программе с повышением – температуры в доме) невозможен, для предотвращения взаимных блокировок можно пронумеровать объекты синхронизации и всегда захватывать их в постоянном порядке. Продолжим аналогию с обедающими программистами: если поток всегда сначала берет нож, а потом вилку, проблем с взаимной блокировкой не будет. Первый поток, захвативший нож, сможет нормально поесть. В переводе на язык программных потоков это означает, что захват объекта 2 возможен лишь при условии предварительного захвата объекта 1
.

Иллюстрированный самоучитель по Visual Basic .NET › Многопоточные приложения › Взаимная блокировка
Рис. 10.7. Анализ взаимной блокировки в окне потоков

Следовательно, если убрать вызов Rnd в строке 98 и заменить его фрагментом:

mFork.GrabFork(Me)
mKnife.GrabKnife(Me)

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

Если Вы заметили ошибку, выделите, пожалуйста, необходимый текст и нажмите CTRL + Enter, чтобы сообщить об этом редактору.