Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 99
Текст из файла (страница 99)
Никому 374 Глава 12 не интересно знать, когда они завершатся, или каким будет результат нх действий. Очевидно, что это тот редкий случай, когда может понадобиться данный поток. В большинстве ситуаций нужно взаимодействовать с работающим потоком, ожидать, пока он достигнет определенного места в коде, или, возможно, работать с одними и теми же экземплярами объектов или значений, с которыми работают и другие потоки. Во всех этих и многих других случаях можно положиться на технику синхронизации, чтобы синхронизировать потоки во избежание состояния состязаний и взаимных блокировок. При состоянии состязаний два потока могут нуждаться в доступе к одному фрагменту памяти, и только один из них может делать это безопасно в заданный момент времени.
В таких ситуациях должен использоваться механизм синхронизации, который в каждый момент времени позволяет только одному потоку обращаться к данным, блокируя другой поток и заставляя его ждать, пока первый не завершит работу. Многопоточная среда стохастична по своей природе, и никак нельзя узнать, когда планировщик заберет управление у вашего потока. Классический примером может служить ситуация, когда один поток, находясь на полпути в изменении блока памяти, теряет управление, а затем другой поток получает управление и начинает читать память, предполагая, что она находится в корректном состоянии. Примером взаимной блокировки может служить ситуация, когда из двух потоков каждый ожидает освобождения ресурса другим потоком.
Оба ждут друг друга, и поскольку ни один из них не может продолжить выполнение, пока не будет выполнено то, чего он ожидает, то оба остаются в состоянии вечного ожидания. Во всех задачах синхронизации должен использоваться наиболее легковесный механизм синхронизации. Например, если предпринимается попытка совместно использовать блок данных между двумя потоками в одном и том же процессе, и нужно разграничить доступ межлу ними двумя, следует применять нечто вроде блокировки Мопэгог (рассматривается далее в главе), а не мцсех.
почему? потому, что блокировка мцсех предназначена для разграничения доступа к разделяемым ресурсам между процессами и представляет собой тяжеловесный объект операционной системы, который замедляет процесс, захватывая и освобождая блокировку. Если не требуется никакой межпроцессной блокировки, используйте вместо нее мопвсог. Даже еще более легковесным, чем мопагог. является набор методов класса Тпсег1оскеб. Он идеален, когда известно, что вероятность ожидания при захвате блокировки является низкой. на заметку! любого рода ожидание на объекте ядра, такое как ожидание на мосек, яеглэрпоге, хтепеязьснап1а1е или любом другом, которое в конечном итоге обеспечивается ожиданием на объекте ядра Чяп32, требует перехода в режим ядра. Такой переход обходится дорого, и по мере воэможности его всегда лучше избегать.
Например, если синхронизируемые потоки находятся в одном и том же процессе, объекты синхронизации ядра, вероятно, чересчур тяжеловесны. Наиболее легкая техника синхронизации предполагает изошренное использование класса тьгеаб1пд. 1псег1осхегь Все его методы полностью реализованы в пользовательском режиме, что позволяет избежать переключений между пользовательским режимом и режимом ядра. Однако применение класса т1тгезб1пд. Тпсег1осхеб может оказаться непростым, поэтому переход к механизму блокировки более высокого уровня, такому как Мопьсог (или любому другому механизму, который не требует переходов в режим ядра и обратно), часто желателен при синхронизации потоков внутри одного процесса. При использовании объектов синхронизации в многопоточной среде блокировку следует удерживать в течение как можно более короткого промежутка времени.
Например, если блокировка синхронизации захватывается для чтения разделяемого экземпляра структуры, а код внутри метода, захватывающего блокировку, использует этот энземпляр структуры для каких-то своих целей, то лучше создать локальную копию структуры в стеке и затем немедленно освободить блокировку, если только это не является логически не- Многопоточность з С№ 375 возможным или ие приводит к снижению произволительиости. В результате ие будут связываться другие потоки в системе, которым нужен доступ к защищенной переменной.
При синхронизации выполнения потока никогда ие полагайтесь иа такие методы, как ТЬгеас(. БоярепП или ТЬгеас(. Беяпме. Как указывалось в предыдущем разделе главы, вызов ТЬгеас(. Боярепс) иа самом деле ие приостаиавливает поток немедленно. Вместо этого поток должеи достигнуть безопасной точки управляемого кода, прежде чем ои сможет прервать выполнение. Также для сиихроииэации потоков никогда ие применяйте ТЬгеас).
Б1еер. Метод Тпгеас(. Б1еер подходит, когда организован некоторый вид цикл опроса иа сутциости вроде аппаратного устройства, которое только что было сброшено и ие в состоянии никого уведомить о том, что оио вернулось в оперативный режим. В этом случае ие имеет смысла непрерывно в цикле проверять состояние устройства. 1Ьраздо лучше ненадолго перевести поток в спящий режим между опросами, чтобы позволить планировщику разрешить выполняться другим потокам. Хотя об этом говорилось в предыдущем разделе, стоить повториться: если вам когда-нибудь приходилось исправлять ошибки синхронизации, добавляя вызов ТПгеас(.
Б1еер в некоторую кюкущуюся случайной точку кода, значит, вы вообще ие решали проблему. Вы просто скрывали ее и усугубляли. Не поступайте подобным образом! Легковесная синхронизация с помощью класса тп~ет1осие6 Те из вас, кто пришел из неуправляемого мира программирования иа ЪЧ1п32 АР1, вероятно. знают о существовании семейства фуикций 1пгег1ос)сес)...
К счастью, эти функции предоставлены в распоряжение разработчиков С№ через статические методы класса 1пгег1ос)сео из пространства имен Буягем. тьгеас(1пс1. иногда при выполиеиии множества потоков возникает необходимость сопровождения одной простой переменной — обычно типа значения, ио, может быть. и объекта — между иесколькими потоками. Например, предположим, что по какой-то причине нужно отслеживать количество работающих потоков в статической целочисленной переменной. Когда поток стартует, ои увеличивает значение этой переменной, а когда завершается — уменьшает это значение.
Очевидно, что понадобится каким-то образом синхронизировать доступ к этому значению, поскольку планировщик может отобрать управление у одного потока и передать его другому, когда первый находится в процессе обиовлеиия значения счетчика. Еще хуже, когда тот же код должен выполияться параллельно иа миогопроцессориой машине. Для этой задачи можно использовать 1пгег1ос)сес(. 1псгепсепг и 1пгег1ос)сес(. Ресгепсепг. Эти методы гарантированно модифицируют значение атомарио среди всех процессоров системы. Рассмотрим следующий пример: азгпу Буягеап пагод Буягем.ТПгеао№пдт роьггс с1аяя Бпггурогпг ягаггс ргсчаге чо1аг11е 1пг повпегтьгеас)я = 0) ягаггс рг1чаге Бапсот гпс( = пем Касс(омО ) рггчаге я аггс чо1с( Кпс)тпгеас)ропе() ( !! Управлять счетчиком потоков и ожидать произвольный !/ промежуток времени от 1 до 12 секунд.
1пгег1ос1еп.1псгемепг( геГ пиаЬегтпгеас)я )т ггу ( Рпг ггме = гпс).пехг( 1000, 12000 ТПгеас(.Б1еер( ггае ); 376 Глава ) 2 Егпа11у ( 1птег1осхеб.ресгещепт( тег пищЬегТЬтеабя )г ) ) ргтнате ятат1с нотб Ерттптеабуппс() ( нпт1е( тгпе ) ( 1п' тптеябсоппт = 0; тптеабСоппт = 1птет1остеб.сощратезхспапое( гег ппщЬетТЬтевбз, О, 0 ); Сопяс1е.нг1теьтпе( "(0) поток(ов) активно", ГЬтеабСоппт Тптеаб.Б1еер( 1000 ятаттс чатб Матс() ( /У Запустить потоки отчетов.
Тптеяб тероттет = пен Тптеаб( пен ТптеабБтагт(ЕптгуРо1пт.РртТЬгеабуппс) ); терсгтет.1явасхдгоипб = ттпе; терсттег.зтагт(); Гг' Запустить потоки, ожидающие в течение случайного периода времени. ТПтеаб[] тпбтптеадя = пеи Тптеаб[ 50 ]г Тот( птпт т = 0; т < 50; ч+т ) ( тпбтпгеабя[1] пеи Тпгеаб( пеи Тптеабятатт(ЕпттуРотпт.япбтпгеабуппс) ); тпбтптеабя[т] .Зтатт (); Эта небольшая программа создает 50 потоков переднего плана, которые не делают ничего, кроме ожьщания в течение произвольного периода времени между 1 и 12 секунд. Она также создает фоновый поток. который выдает отчет о том, сколько потоков активно в настоящий момент. Если взглянуть на метод Епбтпгеабуппс, являющийся функцией потока, которую используют 50 потоков программы, в нем можно увидеть инкрементирование и декрементирование целочисленного значения с использованием методов 1птет1осйеб, Обратите внимание на применение блока 11па11у, который гарантирует уменьшение счетчика, независимо от того, как аавершится поток.
Можно было бы воспользоваться шаблоном Р(зроваЫе с помощью ключевого слова пя1по, упаковав инкремент и декремент счетчика в отдельный класс, реализующий 101яро яаЬ1е. Это позволило бы избавиться от громоздкого блока 11па11у. Но в этом случае он не поможет, так как необходимо создать ссылочный тип, который будет хранить целочисленную переменную счетчика, поскольку невозможно применять теЕ к целому числу как к полю вспомогательного класса. На заметку( Относительно кода предыдущего примера Джон Скит (Зол Якее1) высказал замечательную мысль, которая подчеркивает важность знания поведения типов в определенных ситуациях. Статическому экземпляру Еапбощ в предыдущем коде примера ничего не известно о потоках.
Поэтому если два потока почти одновременно вызовут его методы, то состояние экземпляра Еапбощ теоретически мсжет Оказаться НЕСоглаСованным, что приведЕт его в мир неопределенного поведения. Джон советует пометить еапбощ в оболочку другого типа, который обеспечит необходимую синхронизацию. Однако в данном примере такой прием затенил бы основную идею. )Ыногопоточность в Сз З77 Выше были показаны методы 1псег1оскег). 1псгегкепс и 1псег1оскег). Ресгеглепт в действии. Но как насчет 1пгег1оскес).















