Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 103
Текст из файла (страница 103)
Рп1зе и моп). гог. Ма1г.. Рассмотрим пример, в котором реализуется механизм квитирования между двумя потоками. Цель — заставить оба потока поочередно увеличивать значение счетчика. пагод Яуягеэп пегас Яуясез.ТЬгеаб1по; РпЬ1гс с1азз ЕпсгуРогпс ( ясасгс рггчасе 1пг соппсег = 01 ясас1с рггчасе оЬ)есс ГЬеьосМ = пеи ОЬ)есс(); ясасгс рггчасе чогб ТЬгеаозопс1() ( 1осн( ГЬеьосг ) ( Тот( гпГ г = 01 г < 501 эаг ) ( Мопгсог.вагс( ГЬеьосн, Т1теопс.1п11п1се ); Сопяо1е.кггсеЬ1пе( "(0) из потока (1)ГЬ еэсоппсег, ТЬгеаб.спггепГТЬгеаб.мапачебТЬгеаб1б ) 1 Мопгсог.Рп1яе( ГЬеЬосг ) 1 ясас1с Рг1часе чогб ТЬгеабуппс2() ( 1осг( ГЬеЬосг ) ( Еог( гпс г = 01 г < 50' эеб ) ( Мопгсог.Рп1яе( гьеЬосн ) 1 Мопбсог.на1Г( ГЬеьосг, Т1зеопс.1п11п1се Сопяо1е.кг1се1,гпе( "(0) из потока (1)ГЬ еесоппсег, ТЬгеаб.СпггепГТЬгеаб.мапачебТЬгеабьб ягаггс чогб Магп() ( ТЬгеаб ГЬгеаб1 = пеи ТЬгеаб( пеи ТЬгеабясагс(ЕпсгуРогпГ.ТЬгеабуппс1) ТЬгеаб ГЬгеаб2 = пеи ТЬгеаб( пеи ТЬгеабзсагс(ЕпсгуРогпГ.ТЬгеабуппс2) гзгеаб1.Ягагг() 1 гьгеаб2.ягагг() 1 ) ) Многопоточность в С» 389 Вывод этого кода показывает, что потоки поочередно увеличивают значение с о оп пег.
Если простой просмотр кода не позволяет понять, как он работает, выполните его пошагово в отладчике. В качестве другого примера можно реализовать черновой пул потоков, используя мопгсог.иа1г и мопггог. РОЬяе. В действительности делать такие вещи излишне, поскольку .]ь]ЕТ Ргашеа7ог]г предоставляет объект ТЬгеас1роо1, который достаточно надежен и использует оптимизированные порты завершения ввода-вывода операционной системы. Однако ниже для примера будет показано, как реализовать пул рабочих потоков, которые ожидают постановки в очередь рабочих элементов. иягпч Яуясеяп пвгпэ Яувсев.ТЬгеаб1пьи овгпд Яувтев.Со11ессгопя; роЫгс с1аяв СгибеТЬгеаброо1 ( всас1с геабоп1у 1пс МахиогХТЬгеабя = 4) вгас1с геабоп1у 1пс Иагстгвеопс = 20001 роЬ11с бе1едасе чогб Иоггбе1еоасе () ) рпЬ|гс СгпбеТЬгеабРоо1() ( ягор = га1яеп ногХЬосг = пеи ОЬ1есс(); ноггбсеое = пен Очесе() 1 Гпгеабв = пен Тпгеаб[ Махноггтпгеабя ]; гог( гпг г = 0; г < махиогхтьгеабя; ++в ) ( Гпгеабв[г] пеи ТЬгеаб( пен Тпгеабэсагс(ГЫв.тпгеабгипс) ) гпгеабв[1].Ягагг(); ) ) рггчасе чогб Тпгеабгипс() ( 1осг( иогХЬосг ) ( бо ( 11( 'вТор ) ( Иоггпе1едасе ногг1сев = пи11) т»( Моп1сог.на1Г(иоггьосг, Иа1ГТ1веоос) ) ( // Обработать элемент из начала очереди.
1осХ( ногХСчесе.эупсэоос ) ( ногХ1Гев = (Иоггпе1еяаге) ногХОоеие.бесселе()) ) ногг1сев()г ] ) нп).1е ( ( вТор ) ! ] риЫгс чогб Ячпвгскогг1Гев( Иоггве1едасе гоев ) ( 1осг( ногХЬосг ) ( 1осг( ноггбэесе.эупспоос ) ( ноггбиече.эпг(сесе( 1сев ); Мопгсог.ри1ве( иогХЬосХ ); ) 390 Глава 12 ров11с чобб БЬггбонп() ( асор = ггпе; ) ргачаге Спеце ногхбцеце; ргачасе ОЬБесг ногхьосул рг1чаге Тпгеаб[) Спгеабзг ргачасе чо1аг11е Ьоо1 асор; ) рцЬ11с с1ааз ЕпггуРоапг ( згаг1с чогб Иогхрцпсг1оп () ( Сопао1е.нгьгеЬ1пе( "Метод Иогкрцпсг1оп() вызван на потоке (О)", Тпгеаб.сцггепгтпгеаб.Мападебтпгеаб1б ); ) эгагбс чогб Маап() ( СгцбеТЬгеаброо1 роо1 = пен СгцбеТЬгеабРоо1(); Бог( глг 1 = Ог 1 < 10г ++1 ) ( роо1.5цЬыагиогх1гет( пен СгцбеТЬгеаброо1.иогкре1едаге(ЕпггуРоапс.ногхрцпсгаоп) ) с'/ Задержка для эмуляции выполнения данным потоком другой работы.
Тпгеаб.51еер( 1000 роо1. Бпцг бонн (); 1 В этом случае рабочий элемент представлен делегатом типа иогкве1едаге, не принимающим параметров и не возвращающим значений. При создании объекта СгцбеТЬгеаброо1 он создает пул потоков и запускает их путем запуска главного метода обработки рабочего элемента. Этот метод просто вызывает мопбгог.
иа1г для ожидания элемента, который будет поставлен в очередь. когда вызывается Бцьльгиогк1сет, элемент помещается в очередь и вызывается мопбгог. Рц1зе для освобождения одного из рабочих потоков. Естественно, доступ к очереди должен быть синхронизироваи. В этом случае для синхронизации доступа к очереди используется ссылочный тип— объект, возвращенный свойством Бупсеоос очереди. Вдобавок рабочие потоки не должны ждать вечно, поскольку они должны периодически просыпаться и проверять флаг, чтобы узнать, не пора ли им изящно завершиться.
Дополнительно можно превратить рабочие потоки в фоновые установкой свойства 1зваскдгоцпб внутри метода яьцсбоып. Однако в этом случае рабочие потоки могут быть завершены перед тем, как они завершат выполнение своей работы. В зависимости от ситуации это может быть или не быть допустимым. В этом примере присутствует один тонкий дефект, препятствующий широкому использованию СгцбеТЬгеас(роо1. Например, что произойдет, если элементы будут помещены в очередь до создания потоков в Сгцбетпгеаброо1? В том виде, как он написан сейчас, СгцбеТЬгеаброо1 утратит возможность отслеживания этих элементов в очереди.
Дело в том, что Моп1С ог не поддерживает состояния, указывающего на то, что был вызван Рц1зе. Таким образом, если Рц1зе будет вызван перед тем, как любой поток обратится к иааг, элемент будет утерян. В данном случае лучше использовать яеыарйоге, о котором речь пойдет в следующем разделе. На заметку! Другой удобный прием сообщения потокам о том, что они должны завершиться, заключается з создании специального вида рабочего элемента, который скомандует потоку завершиться.
Трюк состоит з том, что необходимо убедиться, что з очередь помещено столько специальных рабочих элементов, сколько есть потоков з пуле. мэогопотоэность з с№ 391 Блокирующие объекты Платформа .НЕТ Рташеиогк предоставляет несколько высокоуровневых блокирующих объектов, которые можно испольэовать для синхронизации доступа к данным из множества потоков. Предыдупвай раздел был полностью посвящен одному типу блокировок — мопьсог. Однако класс мопьгог не реализует объект блокировки ядра; вместо этого он предоставляет доступ к блокировке синхронизации каждого экземпляра объекта .ХЕТ.
Ранее в этой главе также рассматривались методы примитивного класса 1пгег1ос Хе ей которые можно применять для реализации спин-блокировок. Одной причиной того, что спин-блокировки можно считать примитивными, является то, что они нереентерабельны и потому не позволяют захватывать одну и ту же блокировку многократно. Другие высокоуровневые блокирующие объекты обычно разрешают это, до тех пор, пока обеспечивается соответствие между количеством операций установки блокировки и количеством операций по ее освобождению.
В настоящем разделе речь пойдет о некоторых полезных блокирующих объектах, предлагаемых .НЕТ Ргашеюогк. Независимо от используемого типа блокирующих объектов. всегда нужно стремиться писать код, который обеспечивает как можно более кратковременное удержание блокировки. Например, если блокировка захвачена для доступа к некоторым данным внутри метода, который может потребовать ощутимого времени для обработки данных, удерживайте блокировку лишь столько времени.
сколько необходимо для создания копии в локальном стеке, и затем как можно скорее снимайте блокировку. Применение такой техники гарантирует, что другие потоки в системе не будут заблокированы в течение слишком длительного времени, ожидая доступа к тем же самь1м данным. яеасуегХ~х Сего ос3с При синхронизации доступа к разделяемым данным между потоками часто возникает ситуация, когда несколько потоков читают, или потребляют, данные, в то время как только один поток записывает, или производит. эти данные. Очевидно, что все потоки должны захватывать блокировку перед обращением к данным, предотвращая состояние состязаний, когда один поток записывает данные, в то время как другой находится в процессе их чтения, потенциально порождая мусор для читателя.
Однако взаимная блокировка определенно выглядит неэффективной для тех потоков, которые просто собираются читать данные, а не модифицировать их. Нет причины, по которой они не могли бы читать данные параллельно, не влияя друг на друга. Элегантно решить описанную выше проблему можно с помощью класса Реас1егнг1гегьос~с. По сути, он позволяет множеству читателей обращаться к данным одновременно.















