Г. Шилдт - С#4.0 Полное руководство (1160795), страница 172
Текст из файла (страница 172)
Напомним, что метод Ха1с () вызывается в обоих методах, Тзс)с () и Тоск (), после вывода соответствующего слова на экран. Но дело в том, что когда часы остановлены, один из этих методов все еще находится в состоянии ожидания. Поэтому завершающий вызов метода Рп1зе () требуется, чтобы выполнить ожидающий метод ло конца. В качестве эксперимента попробуйте удалить этот вызов метода Рн1зе () и понаблюдайте за тем, что при этом произойдет. Вы сразу же обнаружите, что программа "зависает", и для выхода из нее придется нажать комбинацию клавиш <С(г)+С>. Дело в том, что когда метод Ха11 () вызывается в последнем вызове метода ТосЕ (), соответствующий ему метод Рп1ве () не вызывается, а значит, выполнение метода Тоск () оказывается незавершенным, и он ожидает своей очереди до бесконечности.
Прежде чем переходить к чтению следующего раздела, убедитесь сами, если, конечно, сомневаетесь, в том, что следует обязательно вызывать методы Ха1С () и Ри1зе (), чтобы имитируемые часы шли правильно. Для этого подставьте приведенный ниже вариант класса ТгсктосК в рассматриваемую здесь программу.
В этом варианте все вызовы методов Хагс () и Рп1ве () исключены. /Г Нерабочий вариант класса тгоктоск. с1авв Т1сктоск ( оЬ)есс 1ооКОп = лен оЬ)есс() рпьггс чогн тгск(Ьоо1 гппп1пд) ( 1оск(1осКОп) ( 11(!гнппгпе) ( О остановить часы геьнгпг ) Сопво1е.Хггге("тик "); ) рпЬ(1с чо1б ТосК(Ьоо1 гнппгпч) ( 1оок(1осКОп) 1Г(!гппп1пч) ( /Г остановить часы геспгп; ) Сопво1е .Хггсет ьпе (г лак"); ! ) ) 860 Часть И.
Библиотека С(г После этой подстановки результат выполнения данной программы будет выглядеть следующим образом. тик тик тик тик тик так так так так так Часы остановлены Очевидно, что методы Тзсх () и Тос)с () больше не синхронизированы! Взаимоблокировка и состояние гонки При разработке многопоточных программ следует быть особенно внимательным, чтобы избежать взаимоблокировки и состояний гонок. Взаимоблокировка, как подразумевает само название, — это ситуация, в которой один поток ожидает определенных действий от другого потока, а другой поток, в свою очередь, ожидает чего-то от первого потока. В итоге оба потока приостанавливаются, ожидая друг друга, и ни один из них не выполняется.
Эта ситуация напоминает двух слишком вежливых людей, каждый из которых настаивает на том, чтобы другой прошел в дверь первым! На первый взгляд избежать взаимоблокировки нетрудно, но на самом деле не все так просто, ведь взаимоблокировка может возникать окольными путями.
В качестве примера рассмотрим класс Т1с(стоо)с из предыдущей программы. Как пояснялось выше, в отсутствие завершающего вызова метода Ро1ве () из метода Тьс)г() или Тоок() тот или другой будет ожидать до бесконечности, что приведет к "зависанию" программы вследствие взаимоблокировки. Зачастую причину взаимоблокировки не так-то просто выяснить, анализируя исходный код программы, поскольку параллельно действующие процессы могут взаимодействовать довольно сложным образом во время выполнения.
Для исключения взаимоблокировки требуется внимательное программирование и тщательное тестирование. В целом, если многопоточная программа периодически "зависает", то наиболее вероятной причиной этого является взаимоблокировка. Состояние гонки возникает в том случае, когда два потока или больше пытаются одновременно получить доступ к общему ресурсу без должной синхронизации. Так, в одном потоке может сохраняться значение в переменной, а в другом — инкрементироваться текущее значение этой же переменной. В отсутствие синхронизации конечный результат будет зависеть от того, в каком именно порядке выполняются потоки: инкрементируется ли значение переменной во втором потоке или же оно сохраняется в первом. О подобной ситуации говорят, что потоки "гоняются друг за другом", причем конечный результат зависит от того, какой из потоков завершится первым.
Возникающее состояние гонок, как и взаимоблокировку, непросто обнаружить. Поэтому его лучше предотвратить, синхронизируя должным образом доступ к общим ресурсам при программировании. Применение атрибута Ме~т1о61шр1А11х~Ьи1е Метод может быть полностью синхронизирован с помощью атрибута Мегпос(1вр1АССг1Ьнге. Такой подход может стать альтернативой оператору 1ос)г в тех случаях, когда метод требуется заблокировать полностью. Атрибут Глава 23. Многопоточное программирование. Часть первая: основы 861 месьоб1шр1Агьг1ьцге определен в пространстве имен Буясеш.нцпсбше.
Сошр11егБегчьсея. Ниже приведен конструктор, применяемый для подобной синхронизации: рцЫ1с Мегьсб1пр1АГ«г1Ьцге(Мегьоб1шр10РС1опя шеслоб1шр10рсбопя) где ше ЬЛ обгшр10р с1оп я обозначает атрибут реализации. Для синхронизации метода достаточно указать атрибут Месноб1шр10рсбопя. Бупснгопьяеб.
Зтот атрибут вызывает блокировку всего метода длд текущего экземпляра объекта, доступного по ссылке СИЯ. Если же метод относится к типу згагбс, то блокируется его тип. Поэтому данный атрибут непригоден для применения в открытых обьектах или классах. Ниже приведена еще одна версия программы, имитирующей тиканье часов, с переделанным вариантом класса Тбс«Тос«, в котором атрибут МесЬоб1шр10рс1опя обеспечивает должную синхронизацию.
// Использовать атрибут Месвоб1шр1АгвгьЬцге для синхронизации метода. цягпо Яуясеш; цягпо Яуясеш.ТЬгеабгпоо цяьпо Яуягеш.яцпгтше.Сошрь1егзегчбсея; О Вариант класса Тгс«Тос«, переделанный с целью // использовать атрибут Мегьоб1шр10рг1опя.яупсьгощтеб. с1аяя Т1с«Тес« ( /* Следукший атрибут полностью синхронизирует метод Тгс«(). */ (Мегьоб1шр1лсвг1Ьцге(мегвоб1шр10ргбопя.зупсЬгопьяеб)] рцЫгс чо1б Тьс«(Ьсо1 гцпп1пя) ( 11(!гцппгпп) ( // остановить часы мопьсог.Рц1яе(ЬЫя)) О уведомить любые ожидающие потоки гетцгп! Сопво1е.нгтге("тик "); Моп1гсг.рц1яе(СЫя); О разрешить выполнение метода Тос«() Мсп1сог.Иаьс(сщя); // ожидать завершения метода Тос«() ) /* Следукший атрибут полностью синхронизирует метод Тос«(). */ [Мегьоб1шр1АССг1Ьцге(негвоб1шр10рггопя.зупсвгоп1яеб)] рцЫТс чоьб Тес«(Ьос1 гцпп1пс) 11(!гцпп1по) ( // остановить часы Мспьссг.рц1яе(сЫв); // уведомить лкбые сжидакшие потоки гесагп; Сспяс1е.нгьгеььпе("так"); Мопггог.рц1яе(ЬЫя); // разрешить выполнение метода Тле«() Мопггог.наес(СЫя)," // ожидать завершения метода Тьс«() ) с1аяя МуТЬгеаб ( рцЫ1с Тьгеаб ТЬгбг Т1с«тес« ГЬСЬ; 862 Часть П.
Библиотека С() Сконструировать новый поток. рпЬ11с Мутьгеяс)(ясгхпч пале, Т1сКТссК ГС) ( Тьгс) = пен Тьгеас(гита.впп)г сгоь Тьгс(.мяте = папе; Тьго.эсягс()г ) О Начать выполнение нового потока. чс1с) Нип() ( тб(тьго.ияпсе == "Тгс)с") ( гог(1пг 1=0г г<5) 1+т) ггоь.т1ск(ггпе)г ГГОЬ.Т1сК(та1яе); ) е1яе ( Гог(1пс 1=0г 1<5г 1++) ГГОЬ.ТссК(сгпе); ггоь.тоск(га1ве)г ) с1аяя Т1сКТпЧС1осК ( ясастс чета Мвтп() ( Т1сКТосК СГ = пен Т1сКТссК()Г Мутьгеас( тс1 = пен Мутьгеас(("Т1СК", СС)г Мутьгеао ыг2 = лен Мутьгеао("тссК", ГГ); ыг1 .тьгс. то1п() г ыг2 .тьгс(. тсгп () г Сспяс1е.нг1сеъ1пе("Часы остановлены"); ) ) Эта версия программы дает такой же результат, как и предыдущая.
Синхронизируемый метод не определен в открытом классе и не вызывается для открытого объекта, поэтому применение оператора 1осК или атрибута Месбос(1пср1йссг1Ьпсе зависит от личных предпочтений. Ведь и тот и другой дает один и тот же результат. Но поскольку ключевое слово 1осК относится непосредственно к языку С(Г, то в примерах, приведенных в этой книге, предпочтение отдано именно ему. ПРИМЕЧАНИЕ Не применяйте атрибут месьос(1гпр1йссг1ьисе в открытых классах или экземплярах открытых объектов. Вместо этого пользуйтесь оператором 1осК, чтобы заблокировать метод для закрытого объекта, как пояснялось ранее. Применение мьютекса и семафора В большинстве случаев, когда требуется синхронизация, оказывается достаточно и оператора 1осК Тем не менее в некоторых случаях, как, например, при ограничении Глава 23. Многопоточное программирование. Часть первая: основы 863 доступа к общим ресурсам, более удобными оказываются механизмы синхронизации, встроенные в среду .НЕТ Ргашеьуог)с.
Ниже рассматриваются по порядку два таких механизма: мьютекс и семафор. Мьютекс Мьютекс представляет собой взаимно исключающий синхронизирующий объект. Это означает, что он может быть получен потоком только по очереди. Мьютекс предназначен для тех ситуаций, в которых общий ресурс может быть одновременно использован только в одном потоке. Допустим, что системный журнал совместно используется в нескольких процессах, но только в одном из них данные могут записываться в файл этого журнала в любой момент времени. Для синхронизации процессов в данной ситуации идеально подходит мьютекс. Мьютекс поддерживается в классе Буз Ьев. ТЬгеаг(гпо.
Ми ге х. У него имеется несколько конструкторов. Ниже приведены два наиболее употребительных конструктора. риЫ1с Михея() риЬЬьс Мисех(Ьсс1 гп1гта11уомпес)) В первой форме конструктора создается мьютекс, которым первоначально никто не владеет. А во второй форме исходным состоянием мьютекса завладевает вызывающий поток, если параметр 1п1 С1 а11уОмпес( имеет логическое значение Ь гне. В противном случае мьютексом никто не владеет. Для того чтобы получить мьютекс, в коде программы следует вызвать метод На1СОле () для этого мьютекса. Метод иа1ЬОпе () наследуется классом Мигах от класса ТЬге аб. Хагс Налс(1е.