Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 101
Текст из файла (страница 101)
Другими словами, блокировка не может быть захвачена более одного раза, как это, например, может сделать критичный блок или мьютекс. Это не значит, что спин-блокировки нельзя использовать с техникой рекурсивного программирования. Это просто означает необходимость освобождения блокировки перед рекурсивным вызовом, в противном случае будет спровоцировано состояние взаимной блокировки. На заметку! Если нужен реентерабельный механизм, можно воспользоваться более структурированными объектами ожидания, такими как класс Мопэсог, который рассматривается ниже, или же объекты ожидания ядра системы.
Кстати, если хотите увидеть некоторый, так сказать, фейерверк, попробуйте закомментировать использование спин-блокировки в методе Епс!тнгеаоуспс и запустить код несколько раз. Скорее всего, вы заметите, что вывод в журнальном файле стал несколько некрасивым. И эта некрасивость усилится при выполнении этого же кода на многопроцессорной машине. Подробнее об этойтехникечитайте в главе 13. Многопоточность в О№ 381 КЛЭСС Яр1пЬос)с В библиотеке .НЕТ 4.0 ВСЬ появился новый тип Яуясегв. Тпгеаб1по. Яр1пЬосК. Его определенно следует испольэовать вместо класса Муз р1пЬо с К, который применялся для примера в предыдущем разделе. Бр1пЬосК должен использоваться, когда есть обоснованные ожидания относительно того, что захват блокировок редко будет связан с ожиданием. Если потоку, использующему Яр1п1.ос(г, придется ждать часто, эффективность пострадает, поскольку будет выполняться частое переключение этих потоков.
Таким образом, когда поток удерживает Яр№пЬоск, он должен делать это в течение насколько возможно короткого времени, любой ценой избегая установки другой блокировки. Кроме того, как и МуЯр1п1,о с К из предьц~щего раздела, блокировка Я р№п1.ос(г ие может быть захвачена рееитерабельио. То есть если поток уже владеет блокировкой, попытка захватить ее опять приведет к генерации исключения, если передано сгпе в качестве параметра епаЬ1еТЬгеабонпегтгасК1пд конструктора Бр1пЬосК,или к возиикиовеиию взаимной блокировки.
На заметку! Отслеживание владельца потока в Бр№пьоск на самом деле предназначено для использования при отладке. При разработке программного обеспечения существует устоявшееся мнение, что причиной всех бед является ранняя оптимизация. Хотя эта истина ие абсолютна и имеет заметные исключения, все же это хорошее эмпирическое правило, которому нужно следовать. Поэтому, вероятно, стоит начинать с использования более высокоуровневых и гибких механизмов блокировки. которые жертвуют эффективиостью в пользу гибкости. Затем, если во время тестирования и профилирования будет определено, что должен использоваться более легковесный механизм блокировки, можно обратиться к Бр1пЬос(г.
На заметку! Бр№пьоск — тип значения. Поэтому соблюдайте осторожность, избегая излишнего копирования и упаковки, иначе могут возникнуть неожиданные сюрпризы. Например, если Бр№пЬос К требуется передать в качестве параметра методу, передавайте его по ссылке (гег(, чтобы избежать лишнего копирования. Чтобы продемонстрировать использование Бр№пЬоск, предыдущий пример был модифицироваи для замены МуБр1п1 о с К иа Бр го Ьос к, как показано ниже: пя№пд Яувгеьм цв1пд Яувгеа.10г ця1пд Яувгэа.ТЬгеаб№пяп рпЬТ№с с1ввв Епггугогп ( вгасгс ргьчасе Квпбсэ гпб = пен Нвпбоп()г ргтчвге вгвг№с ЯР1пьосК 1ооьоск = пен ЯргпЬоск( гв1яе ргзчвсе вгвс1с Бггевэиггсег гвьоо = пен Ясгевпиг№гег( Р11е.орел("1оч.вхв", Р11енобе.лррепб, Г11елссеяя.иг1се, Р11езпаге.попе! рг№часе ягвг№с чогб КпбТЬгевбГопс(! ( Ьоо1 1осКТвКеп = тв1яег 1ооьосК.Епвег( гет 1осКТаКеп (г 382 Глава 12 11( 1освтзвеп ) ( ггу ( ГяЬод.нг1секьпе( "Поток запускается" ); Бякой.Е1пяЬ()) ) Ттпз11у ( 1оБЬосв.кх11(); ) ) Тпс Гвше = гпб.иехс ( 10, 200 ) ) ТЬгезб.Б1еер( Гкше ); 1осгтзгеп = Га1яев 1одкосг.впгег( гег 1освтзхеп )> 11( 1осхтавеп ) ( ггу ( Гякоч.кг1секьпе( "Поток завершается" ); Тяьос.Р1пяЬ(); ) Гьпа11у ( 1одЬосК.Кхьс(); ) ) ) ягягьс чокб Матп() ( уу Запустить потоки, охндзшлме я течение случайного периода времени.
Тьгеаб(] гпбслгезбя = пен ТЬгеаб[ 50 Бог( и1пг 1 = О> 1 < 50; зч1 ) ( гпбскгеабя[1] пен ТЬгеаб( пен ТЬгезбксягс( ЕпсгуРотпГ.КпбТЬгезбуппс) гпбГЬгезбя[1].БГагс()) В коде есть очень важные моменты, на которые следует обратить внимание. Вызов Бргп1 ос)г. Епгег принимает ссылку гек на переменную Ьоо1. Эта переменная Ьоо1 указывает на то, была ли установлена блокировка. Таким образом, после вызова Епсе г она должна быть проверена. Но что более важно — перед вызовом кпгег эта переменная Ьоо1 должна быть инициализирована значением Та1яе.
В классе Бр1пЬос]г не реализован интерфейс 101зрозаЬ1е, поэтому он не можете использоваться вместе с блоком паапа, так что вместо гарантии правильной очистки применяется конструкция ггу/ Тьпа11у. Если бы команда разработчиков ВСЬ реализовала 101ярояявье в ЯргпЬосх, пришлось бы ожидать катастрофы. Дело в том, что всякий раз, когда выполняется приведение экземпляра к типу реализуемого им интерфейса, производится упаковка типа значения. Упаковка крайне нежелательна для экземпляров Ярьг1.осХ, и ее следует всячески избегать. КЛВСС МОП~ 1ОХ В предыдущем разделе было показано, как реализовать спин-блокировку, используя методы класса 1пгег1осхеб.
спин-блокировка не всегда является самым эффективным механизмом синхронизации, особенно в среде, где синхронизация почти гарантирована. Планировщик потоков должен будить поток н позволять ему повторно проверять переменную блокировки. Как упоминалось ранее, спин-блокировка идеальна, Многопоточность в О№ 383 когда необходим легковесный нереентерабельный механизм, и шансы того, что потоку придется ждать, невелики.
Если известно, что вероятность ожидания высока, должен применяться механизм синхронизации, позволяющий планировщику обойтись без пробуждения потока до тех пор, пока доступна блокировка. Для обеспечения синхронизации между потоками в пределах одного процесса в .ХЕТ предлагается класс яузсегп. тьгеап(1по. мопзгог. Его можно использовать для защиты доступа к определенным переменным или для разграничения доступа к колу, который должен быть запущен только в одном потоке в единицу времени. Ма заметку! Класс мопзсог обеспечивает синхронизацию, при которой в единицу времени будет выполняться только один метод или блок защищенного кода. Мосек обычно служит для той же цели. Однако мопзсог намного легче и быстрее.
моп ьог подходит, когда требуется защитить доступ к коду внутри одного процесса, а мотах — когда необходимо защитить доступ к ресурсу из множества процессов. Один потенциальный источник путаницы, связанной с классом Моп1сог, состоит в невозможности создания экземпляра этого класса. Класс Мопзгог, во многом подобно классу 1пгег1оскег(, представляет собой просто включающее пространство имен для коллекции статических методов, осуществляющих всю необходимую работу. Если вы привыкли к использованию критических секций в %гш32, то вам известно, что в некоторой точке должна быть распределена и инициализирована структура СЕ111С)(1 яЕСТ10М.
После этого для входа и выхода из блокировки вызываются йт(п32-функпни ЕпсегСг111са1Яесс гоп и ЬеачеСгзс1са1Яессьоп. Ту же задачу можно решить с использованием класса Моп1гог в управляемой среде. Для входа и выхода из критической секзтни вызываются методы Мопзгог. Епгег и Мопзгог.
Ех11. Там, где передается объект СЕ111СЛЬ ЯЕСТ10М функциям критической секции й)(п32, будет передаваться ссылка на объект методам Мопьгог. Внутренне СЬК управляет блоком синхронизации для каждого экземпляра объекта в процессе. По сути, это флаг того же рода, что и целочисленное значение, которое использовалось в примерах предыдущего раздела. посвященного классу 1пгег1оскес(. При получении блокировки на объекте флаг устанавливается. Когда блокировка снимается, флаг сбрасывается. класс мопзсог — это ворота доступа к этому флагу. непостоянство атой схемы проявляется в том, что каждый экземпляр объекта в СЬК потенциально содержит одну из таких блокировок.
"Потенциально" — так как СЬК выделяет их в "ленивом" режиме, поскольку блокировка не каждого объекта будет использована. Все, что потребуется сделать для реализации критической секции — это создать экземпляр яузгегп. Оьб есс. Рассмотрим пример применения класса мопзпог, модифицирован пример из предыдущего раздела: озгпд Яузпепи иззпд Яузпем.тпгеаг(1по) рсЬ11с с1азэ Еппгуро1пп згакгс рг1чаке геаг)оп1у оЬЯесп СпеЬосх = пен ОЬЯесп()г зсапзс рг1чаге Тпс ппеЬегтпгеап(з = 0; зпагзс рг1чзсе капп(от гпп( = пен капп(ои О г ргзчасе зпзг1с чозп Кпп(тпгеэп(рппс О // Управлять счетчиком потоков и ожидать произвольный // промежуток времени от 1 до 12 секунд.
Моппгог.копет( ПЬеЬоск )и ггу ++пскЬегтпгеап)з; 384 Глаза(2 йупа11у ( Мопбгог.ЕхЬС( СЬеьосК ); ) апг С1ще = гпб.кехС( 1000, 12000 ) ) Тсгеаб.Я1еер( С1ще ); Моптгог.ЕпСег( СЬеьасК )) сгу ( --пащоегтьгеабз; ) Тбпа11у ( Мопбяог.ЕхЬС( СЬеьосК ) ) ) рггчаге яоаСЬс чобб КРСТЛгеабгипс О ( нЬ11е( Сгае ) ( Ьпя СЬгеабСоппС = О) Мопбгог.Епгег( СЬеЬосК ); сгу ( СЬгеабСоапг = ппщЬегТЬгеабя; ) Гтпа11у ( Мопугог.ЕхЬС( СЬеЬосК ); ) Сопяо1е.нгЬСеьспе( "(0) поток(ав) активно", сагеабСаапс Тогеаб.Я1еер( 1000 яСагтс чогб Ма1п() /У Запустить потоки отчетов. Тсгеаб героггег = пен ТЬгеаб( пен ТсгеабБСагг( ЕпСгуротпг.крСТЬгеабгапс) героггег.1явасхдгоапб = Сгпе; герогаег.Ясагс(); О Запустить потоки, ожидающие в течение случайнога периода времени.














