Г. Шилдт - С# 3.0 Полное руководство. 2010 (1160798), страница 161
Текст из файла (страница 161)
В начале метода тхсК () проверяется значение текущего параметра, которое служит явным признаком остановки часов. Если это логическое значение Та1зе, то часы остановлены. В этом случае вызывается метод Рп1зе (), разрешающий выполнение любого потока, ожидающего своей очереди. Мы еще вернемся к этому моменту в дальнейшем. Если же часы идут при выполнении метода тйсК (), то на экран выводится слово "тик" с пробелом, затем вызывается метод Рп1зе (), а после него — метод кайс () . При вызове метода Рп1зе () разрешается выполнение потока для того же самого объекта, а при вызове метода Иа1С () выполнение метода Т1ск () приостанавливается до тех пор, пока метод Рп1зе () не будет вызван из другого потока.
Таким образом, когда вызывается метод Тхск (), отображается одно слово "тик" с пробелом, разрешается выполнение другого потока, а затем выполнение данного метода приостанавливается. Метод тосК () является точной копией метода ТТсК (), за исключением того, что он выводит иа экран слово "так".
Таким образом, при входе в метод тоск () на экран выводится слово "так", вызывается метод Рп1зе (), а затем выполнение метода тосК () приостанавливается. Методы Т1сК() и Тоск() можно рассматривать какпоочередносменяющие друг друга, т.е, они взаимно синхронизированы. Когда часы остановлены, метод Ри1зе () вызывается для того, чтобы обеспечить успешный вызов метода Иахс () . Напомним, что метод ИаТЬ () вызывается в обоих методах, Тхск () и Тоск (), после вывода соответствующего слова иа экран. Но дело в том, что когда часы остановлены, один из этих методов все еще находится в состоянии ожидания. Поэтому завершающий вызов метода Ри1зе () требуется, чтобы выполнить ожидающий метод до конца.
В качестве эксперимента попробуйте удалить этот вызов метода Рп1зе () и понаблюдайте за тем, что при этом произойдет. Вы сразу же обнаружите, что программа "зависает", и для выхода из иее придется нажать комбинацию клавиш <Ссг)+С>. Дело в том, что когда метод Иа1Ь () вызывается в последнем вызове метода Тоск (), соответствующий ему метод Рп1зе () не вызывается, а значит, выполнение метода тосК () оказывается незавершенным, и он ожидает своей очереди до бесконечности.
Прежде чем переходить к чтению следующего раздела, убедитесь сами, если, конечно, сомневаетесь, в том, что следует обязательно вызывать методы Иахг () и Ри1зе (), чтобы имитируемые часы шли правильно. Для этого подставьте приведенный ниже вариант класса Т1сктосК в рассматриваемую здесь программу. В этом варианте все вызовы мето- довиа1Ь() и Ри1зе() исключены. // Нерабочий вариант класса ТъсКТоск. с1авз Т1сКТосК ( оЬ)есо 1осхэп = пен опзесо() Глава 23.
Многопоточное программирование 813 ривггс чо1а тгси(Ьоо1 гиппгпп) ( 1осн(1осиэп) ( 1г()гиппгпч) ( // остановить часы гегигпг ) Сопво1е.нггге("тик ")г ) рпЬ1гс чогг( тоси(Ьоо1 гоппгпс) ( 1осх(1осксп) ( гп()гипп1по) ( // остановить часы гегнгпт ) Сопво1е.иггсе)ьпе("'так")т ) ) ) После этой подстановки результат выполнения данной программы будет выглядеть следующим образом: тик тик тик тик тик так так так так Часы остановлены Очевидно, что методы твсх () и Тос)г () больше не синхронизированы! Взаимоблокировка и состояние гонки При разработке многопоточных программ следует быть внимательным, чтобы избежать взаимоблокировки и состояний гонок. Взаимоблокировка, как подразумевает само название, — это ситуация, в которой один поток ожидает определенных действий от другого потока, а другой поток, в свою очередь, ожидает чего-то от первого потока.
В итоге оба потока приостанавливаются, ожидая друг друга, и ни один из них не выполняется. Эта ситуация напоминает двух слишком вежливых людей, каждый из которых настаивает на том, чтобы другой прошел в дверь первым! На первый взгляд избежать взаимоблокировки нетрудно, но на самом деле не все так просто, ведь взаимоблокировка может возникать окольными путями. В качестве примера рассмотрим класс Т1схтосх из предыдущей программы.
Как пояснялось выше, в отсутствие завершающего вызова метода Ри1ве () из метода Твс)г() или Тос)г() тот или другой будет ожидать до бесконечности, что приведет к "зависанию" программы вследствие взаимоблокировки. Зачастую причину взаимоблокировки не так-то просто выяснить, анализируя исходный код программы, поскольку параллельно действующие процессы могут взаимодействовать довольно сложным образом во время выполнения. Для исключения взаимоблокировки требуется внимательное программирование и тщательное тестирование.
В целом, если многопоточная программа периодически "зависает", то наиболее вероятной причиной этого является взаимоблокировка. 814 Часть )!. Библиотека СЗ Состояние гонки возникает в том случае, когда два и более потока пытаются одновременно получить доступ к общему ресурсу без должной синхронизации. Так, в одном потоке может сохраняться значение в переменной, а в другом — инкрементироваться текущее значение этой же переменной. В отсутствие синхронизации конечный результат будет зависеть от того, в каком именно порядке выполняются потоки; инкрементируется ли значение переменной во втором потоке или же оно сохраняется в первом. О подобной ситуации говорят, что потоки "гоняются друг эа другом", причем конечный результат зависит от того, какой из потоков завершится первым.
Возникающее состояние гонок, как и взаимоблокировку, непросто обнаружить. Поэтому его лучше предотвратить, синхронизируя должным образом доступ к общим ресурсам при программировании. Применение атрибута Ме 1ЬойХтр1А~1х хЬи~е Метод может быть полностью синхронизировав с помощью атрибута МеГЬо61вр1АГГТТЬпге. Такой подход может стать альтернативой оператору 1оск в тех случаях, когда метод требуется заблокировать полностью.
Атрибут ме спок(1вр1Ассг1Ьпсе определен в пространстве имен Яузтев. Нцпсуве. Совр11ег5егч1сея. Ниже приведен конструктор, применяемый для подобной синхронизации. рпЬ11с МеГЬоб1вр1АГГгТЬпсе(Меспоб1вр10рс1опя орс) где орс обозначает атрибут реализации. Для синхронизации метода достаточно указать атрибут Мегноб1вр10ргуопя.
Яупспгоп1кег1 Этот атрибут вызывает блокировку всего метода для текущего экземпляра объекта, доступного по ссылке Гнъя. Если же метод относится к типу я гас го, то блокируется его тип. Поэтому данный атрибут непригоден для применения в открытых объектах или классах. Ниже приведена еще одна версия программы, имитирующей тиканье часов, с переделанным вариантом класса ТусКТосК, в котором атрибут Мегноб1вр10рг1опя обеспечивает должную синхронизацию. // использовать атрибут месьоб1вр1Агггъьиге для синхронизации метода.
ия1пд Яуягегы ня1пд 5уягев.ТЬгеабьпдт няъпч яуягев.кипг1ве.совр11егяегчъсезт // Вариант класса ТъсКТосК, переделанный с целью // использовать атрибут Меспо61вр10рсьопя.яупспгопакеб. с1аяя ТьсКТосК ( /* Следующий атрибут полностью синхронизирует метод ТъсК(). */ (МеГЬоб1вр1АГгг1Ьпсе(мегЬоб1вр10рс1опя.яупспгопъкеб)! рнЬ1гс чогб Т1сК(Ьоо1 гпппьпд) (' 11(!гпппъпд) ( // остановить часы Мопъсог.рп1яе(снгя)т // уведомить любые ожидающие потоки геспгпт ! Глава 23. Многопоточное программирование ш15 Сопяо1е.иггое("тик ")т Иопгсог.Рп1зе(ГЬТя)т // разрешить выполнение метода Тосн() мопгаог.наго(ГЬгя)т // ожидать завершения метода ТосК() ) /* Следующий атрибут полностью синхронизирует метод ТосК().
*/ (Месноб1вр1аосг1Ьпсе(Меппобгвр1Орс1опз.Яупснгоп1геб)! рпб11с чогб тосК(поо1 гппп1пп) ( 1Т((гипп1пд) ( // остановить часы Мопгсог.Рп1яе(ГЬгв)т // уведомить любые ожидающие потоки гегпгпт ) Сопво1е.Хггпетапе("так")г Мопгсог.рп1яе(гмая)т // разрешить выполнение метода Тгсх() Моп1саг.на1Г(ГЬ1з)г // ожидать завершения метода Т1сК() ) с1аяв Мутнгеаб ( рпЬ1гс Тнгеаб ТЬгоМ ТгсКТосК ГГОЬт // Сконструировать новый поток. рибагс Иутпгеаг((вог1пр пате, ТгсКТосК ГГ) ( ТЬгб = лен ТЬгеаб(КЬ1в.кпп)г ГГОЬ = ГГ) ТЬгб.нате = пашет ТЬгб.асагс()г ) // Начать выполнение нового потока.
чогб Кпп() ( 1Т(ТЬгб.нате == "Т)сК") ( Тот(гпо а=От г<5г 1++) ГГОЬ.ТгсК(сгпе)т ГГОЬ.Т1сК(га1ве)г ) е1яе ( Тот(апс 1=От г<5; 1ЬЬ) ГГОЬ.ТосК(сгпе)) ггбь.тоск(Еа1зе)г ! ) с1аяя Т1сК1пцС1осК ( япас1с чо10 Маап() ( ТгсКТосК ГГ пен Т1сКТосК()) МуТЬгеаб вс1 = пен Иутпгеаг(("Т1сК", ГГ)т МуТЬгеаб вс2 = пен МуТЬгеаб('"Тоси", ГГ)т тс1.ТЬгб.оо1п() т 616 часть и. Библиотека сз ыс2. Твга.