Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 104
Текст из файла (страница 104)
но как только одному из потоков понадобится записать данные, все, кроме писателя, должны немедленно остановиться. Кеаоегнг1гегЬосх управляет таким поведением, используя две внутренних очереди: одну — для ожидающих читателей и другую — для ожидающих писателей. На рис. 12.2 показана диаграмма высокоуровневой блокировки, как она выглядит внутри Кеаоегнг1гегьосК. В этом сценарии в системе выполняются четыре потока, и в данный момент ни один из них не пытается обратиться к данным в блокировке. Чтобы обратиться к данным, читатель вызывает Асчэ1геаеабегьосх.
При состоянии блокировки, показанном на рис. 12.2, читатель будет немедленно помещен в категорию владельцев блокировки. Обратите внимание, что может существовать несколько владельцев блокировки чтения. Все становится интереснее. как только потоки пытаются захватить блокировку записи, обращаясь к Асопьгенгьгег1.осй. В этом случае писатель помещается в очередь писателей, поскольку читатели в данный момент владеют блокировкой, как показано на рис. 12.3. 392 Глава 12 Поток Поток А Поток Поток в Рис. 12.2. Незадействованная блокировка кеаоегигьсегьосй Рис. 12.3.
Поток-писатель, ожидающий Веабегиггсегьосй Как только все читатели освободят свои блокировки вызовом де1еззекеабегьоск, писатель — в данном случае поток  — сможет войти в область владельцев блокировки. Но что случится, если поток А освободит свою блокировку чтения и затем попытается заново захватить блокировку читателя прежде. чем писатель получит шанс захватить блокировкуу Если поток А сможет повторно захватить блокировку, то любой поток, ожидающий в очереди, сможет потенциально пострадать от блокировки.
Чтобы избежать этого, любой поток, который пытается затребовать блокировку чтения во время нахождения писателя в очереди писателей, помещается в очередь читателей, как показано на рис. 12.4. Многопоточность в СВ 393 Рис. 12.4. Читатель пытается повторно захватить блокировку Естественно, данная схема отдает преимущество очереди писателей. Это имеет смысл. с учетом того факта. что любой читатель хотел бы получить наиболее свежую информацию. Конечно, если поток, нуждающийся в блокировке записи, вызывал бы Асццагеиг1сегЬосх. пока АеабегнгасегЬосх пребывает в состоянии, показанном на рис. 12.2, он бы немедленно был помещен в категорию владельцев блокировки, без необходимости проходить через очередь писателей. Блокировка АеабегигасегЬосх является реентерабельной. Поэтому поток может многократно вызывать любой из методов захвата блокировки. до тех цор, пока этому количеству вызовов будет соответствовать количество вызовов методов ее освобождения.
Всякий раз, когда блокировка запрашивается заново, увеличивается значение ее внутреннего счетчика. Должно быть очевидным, что один поток не может одновременно владеть квк блокировкой чтения, так и блокировкой записи, как и не может ожидать сразув двух очередях Еезбегнг1сегЬос)с. Внимание! Если поток владеет блокировкой чтения и затем вызывает Асс!и1гехг1геьос!с с бесконечным таймаутом, этот поток попадет в ситуацию взаимной блокировки, ожидая от самого себя освобождения блокировки чтения. Поток может повышать или понижать тип блокировки, которой он владеет.
Например, если поток в данный момент владеет блокировкой читателя и вызывает Гтрсугабетоиг1сегЬос!с, то его блокировка читателя освобождается независимо от значения счетчика блокировок и затем помещается в очередь писателей. С!рдгабеТоиг1сегЬос!с возвращает объект типа Ьос!ссоо!с1е. Этот объект должен быль сохранен и передан ронпсугабеггоангагегЬос!с, когда операция записи аавершается. Аеасгегигасег1ос!с использует соо!бе-набор для восстановления счетчика блокировки чтения на объекте. Несмотря на возможность увеличения счетчика блокировок записи после получения его через ГтрдгабеТоиг1сегЬос!с, вызов ронпдгабеГголсиг1сегЬосх освободит блокировку записи, независимо от значения ее счетчика.
Поэтому лучше не полагаться на счетчик внутри "повышенной" блокировки записи. Как почти с любым другим объектом синхронизации в .А!ЕТ Ргаспеиог!с, для практически всех методов захвата блокировки можно задавать таймауг. Таймаут указывается в миллисекундах. Однако вместо того, чтобы возвращать булевское значение, обозначающее успех или неудачу захвата блокировки, при истечении таймаута эти методы генерируют исключение типа Арр11сасаопехсерсьоп. Поэтому при передаче одному иэ этих методов значения таймауга, отличного от тавеоиг.
1гсгапаге, не забудьте поместить вызов в блок г гу, чтобы перехватывать потенциальные исключения. 394 Глава 12 ЕеайедугУе1ЕегйосМЯддт В .)с)ЕТ 3.5 был представлен новый стиль блокировки читателей и писателей, называемый Ееассетит1тетьосхя11в. Он привнес с собой несколько усовершенствований, включая улучшенную защиту от взаимных блокировок, повышенную эффективность и возможность освобождения. Кроме того, по умолчанию он не поддерживает рекурсию, что добавляет эффективности.
Если рекурсия все-тани нужна, то на этот случай в Ееассетит1тетьосхЯ11в предусмотрен перегруженный конструктор, принимающий значение из перечисления Ьосккесптз1опго11су. В М!сгоэой рекомендуют применять во всех новых разработках Ееассетнт1тетьосхя11в вместо Ееассетнт1тетЬосх. Что касается Ееаое тХта те тЬо с Хя11в, то поток может находиться в следующих четырех состояниях: ° свободный (ппйе)б); ° режим чтения (геас) шос)е); ° обновляемый режим (ирс)гас)еаЫе шос1е); ° режим записи (сэПГе шос)е). Свободный режим означает, что поток вообще не пытается ни читать, ни записывать ресурс. Если поток находится в режиме чтения.
он имеет доступ к ресурсу по чтению после успешного вызова метода ептетееассьосх. Аналогично, если поток находится в режиме записи, он имеет доступ на записи после успешного вызова Ептетнт1теЬосх. Как и в случае Ееасгеткт1тетЬосх, в каждый момент времени в режиме записи может находиться только один поток, и пока нотон находится в режиме записи, все потоки блокируются от входа в режим чтения. Естественно, поток, пытающийся войти в режим записи, блокируется до тех пор, пока любой другой поток находится в режиме чтения.
Как только этот другой поток завершит чтение, поток, оясидающий права записи, освобождается. А что собой представляет обновляемый режим7 Обновляемый режим удобен. если есть поток, который нуждается в доступе по чтению н ресурсу, но при этом ему может понадобиться и доступ по записи к тому же ресурсу. Без обновляемого режима потоку приходилось бы выходить из режима чтения и затем следом пытаться войти в режим записи. За время его пребывания в свободном режиме другой поток может войти в режим чтения и тем самым заблокировать последующую попытку входа в режим записи.
В каждый момент времени в обновляемом режиме может находиться только один поток, и он входит в этот режим посредством вызова Еотетярдтас1еаЬ1енеассЬосх. Обновляемые потоки могут входить в режим чтения или записи ренурсивно. даже для экземпляров Ееассетит1тетЬосХЯ11в, которые были созданы с выключенной рекурсией.
По сути, обновляемый режим является более мощной формой режима чтения, обеспечивающей повышенную эффективность при входе в режим записи. Если поток пытается войти в обновляемый режим, а другой поток в это время находится в режиме записи, либо есть потоки в очереди на вход в режим записи, то поток, вызывающий ЕптетССрдтаоеаЬ1енеаоьосСс, будет заблокирован до тех пор, пока другой поток не выйдет из режима записи и все потоки, ожидающие в очереди, не войдут и выйдут из режима записи. Это поведение идентично и для потоков, пытающихся войти в режим чтения.
Ееабе ткт1тетЬоскя11в в некоторых ситуациях может сгенерировать исключение ЬоскнесптзтопЕхсертаоп. Поскольку экземпляры Ееассетнт1тетЬосхЯ11в по умолчанию не поддерживают рекурсию, попытка многократно вызвать Ептеткеасгьосх, Ептетит1теЬосх или ЕптетССрдтасСезЬ1екеаоьоск из одного и того же потока приведет н генерации одного из этих исключений. Вдобавок независимо от того, поддерживает экземпляр рекурсию или нет, поток, уже находящийся в обновляемом режиме и пытаю- щийся вызвать ЕпсегпезсгЬосн, или поток. находящийся в режиме записи и пытающийся вызвать ЕпсегяезбЬосн, могут привести систему к состоянию взаимной блокировки, поэтому исключение Ьоснпесигз1опЕхсерсгоп генерируется и в этих случаях.
Если вы знакомы с классом Мопггог, то можете узнать здесь идиому, представленную в именах методов ЕезбегнгбгегЬосК311пь Всякий раз, когда поток входит в состояние, он должен вызвать один из методов Епсег..., н всякий раз, когда он покидает состояние, он должен вызвать один из соответствующих методов ехгс... Вдобавок, подобно мопгсог, класс ееабегигбгегьоскБ11п предоставляет методы, которые позволяют предпринять попытку войти в блокировку, не рискуя заблокировать навсегда, с помощью таких методов, как ТгуЕпгегпезггЬосн, ТгуЕпсегпргТгзсГезЬ1екезсГЬоони тгуепсегиггсеьосй.
Каждый метод тгу... позволяет передать ему значениетаймаута. указывающее период времени, который вы готовы ждать. Общее правило, связанное с использованием мопгсог, состоит в том, чтобы не использовать моп1сог напрямую, а работать с ним опосредованно — через ключевое слово 1осй. Тогда не придется заботиться о том, чтобы не забыть вызвать мопггог. ехгс, и не нужно набирать блок Тгпз11у, чтобы гарантировать вызов мопгсог.
ехгс прн любых обстоятельствах. К сожалению, эквивалентного механизма облегчения установки и снятия блокировок при использовании Ееабегиг1сегЬосК811я не существует. Всегда будьте осторожны с вызовом методов Ехгс... по завершении работы с блокировкой, и вызывайте их из блока Г1пз11у, чтобы их вызов произошел даже в исключительных случаях. Игзеезг Объект Мосек — тяжеловесный тип блокировки, которую моясно использовать для реализации взаимно исключающего доступа к ресурсам. В .НЕТ РгзшеюогК поддерживаются два типа реализаций Мосек. Если он создается без имени, то вы получаете то, что называется локальным мьютексом (шп1ех).











