Г. Шилдт - С# 3.0 Полное руководство. 2010 (1160798), страница 159
Текст из файла (страница 159)
Но она пригодна только в том случае, если Сбвз является ссылкой на закрытый объект. В связи с возможными программными и концептуальными ошибками, к которым может привести конструкция 1ос)г (сЫз ), применять ее больше не рекомендуется. Вместо нее лучше создать закрытый объект, чтобы затем заблокировать его. Именно такой подход принят в примерах программ, приведенных далее в этой главе.
Однако в унаследованном коде С(Г могут быть обнаружены примеры применения конструкции 1осх (св1з) . В одних случаях такой код оказывается безопасным, а в других — требует изменений во избежание серьезных осложнений при его выполнении. В приведенной ниже программе синхронизация демонстрируется на примере управления доступом к методу 5птпгс (), суммирующему элементы целочисленного массива. // Использовать блокировку для синхронизации доступа к объекту. оьдпр зузтелп озъпд зузтеи.тнтеабепр; с1азз зоиаггау ( 804 Часть П. Библиотека С№ гпс яив) оЬбест 1осКОп = пен оЬЯесс()( // закрытый объект, // доступный для последующей блокировки риЫгс 1пт Яив1Г(гпт[) пива) [ 1оск(1осКОп) ( // заблокировать весь метод яив = 0; // установить исходное значение суммы Тот(1пс 1=0( 1 < пшпя.Ьепцспк 1++) ( яив += пива[к]; сопяо1е.итггеь№пе["текущая сумма для потока " + ТЬгеаб.СиггепГТЬгеаб.наше + " равна " ь яив); ТЬгеаб.51еер(10); // разрешить переключение задач гесигп яшп( ) ) с1аяя МуТЬгеаб ( риЬ1гс Тптеаб Тптбк 1пг[] а; 1пг апяиетк // Создать один объект типа Биватгау для всех // зкземпляров класса МУТЬтеаб.
ягатгс Яиваггау яа = пен Яиватгау()к // Сконструировать новый поток. риЫ1с Мутпгеаб(ясггпд паве, 1пс[) пива) ( а = пива( Тпгб = пен ТЬгеаб(гЫя.аип); Тпгб.шаве = паве; ТЬгб.5тагт(); // начать поток ) // Начать выполнение нового потока. чо1б Нип() ( Сопяо1е.шт1теЬТпе(ТЬтб.наше + " начат."); апянет = яа.яив1Г(а); Сопяо1е.Иггте11пе("Сумма для потока " ь ТЬтб.ыаве + " равна " + апвнег)( Сопяо1е.шг1геЬТпе(ТЬгб.наше + " завершен.")1 ) с1аяя Бупс ( всасгс но1б Магп() ( гпс(] а = (1, 2, 3, я, 5); МУТЬтеаб вт1 = пеы МуТЬгеаб("Потомок №1", а); Глава 23. Многопоточное программирование 805 Мутнгеаг(шС2 = пем Мутлгеаг((" Потомок $2", а); шс1 .
тпгс). тоьп () г шн2.тлгг(.эо1п()г ) ) Ниже приведен результат выполнения данной программы, хотя у вас он может оказаться несколько иным. Потомок Ф1 начат. текущая сумма для потока Потомок В2 начат. Текущая сумма для потока Текущая сумма для потока Текущая сумма для потока Текущая сумма для потока Текущая сумма для потока Сумма для потока Потомок Потомок Е1 завершен. Текущая сумма для потока Текущая сумма для потока Текущая сумма для потока Текущая сумма для потока Сумма для потока потомок Потомок Е2 завершен.
Потомок В1 равна 1 Потомок В1 равна 3 Потомок В1 равна б Потомок В1 равна 10 Потомок В1 равна 15 Потомок В2 равна 1 В1 равна 15 Потомок В2 равна 3 Потомок Е2 равна б Потомок Е2 равна 10 Потомок $2 равна 15 Ф2 равна 15 Как следует из приведенного выше результата, в обоих потоках правильно подсчитывается сумма, равная 15. Рассмотрим эту программу более подробно. Сначала в ней создаются три класса. Первым из них оказывается класс 5пшдггау, в котором определяется метод Пиш1с (), суммирующий элементы целочисленного массива.
Вторым создается класс мутпгеас(, в котором используется статический объект за типа Поест)ггау. Следовательно, единственный объект типа Ппшдггау используется всеми объектами типа Мутпгеас(. С помощью этого объекта получается сумма элементов целочисленного массива. Обратите внимание на то, что текущая сумма запоминается в поле зпш объекта типа япшдггау. Поэтому если метод Ппш1с () используется параллельно в двух потоках, то оба потока попытаются обратиться к полю зпш, чтобы сохранить в нем текущую сумму. А поскольку это может привести к ошибкам, то доступ к методу Ппш11 () должен быть синхронизирован. И наконец, в третьем классе, Пупс, создаются два потока, в которых подсчитывается сумма элементов целочисленного массива.
Оператор 1осн в методе Ппш1С () препятствует одновременному испольэованию данного метода в разных потоках. Обратите внимание на то, что в операторе 1осх объект 1осх()п используется в качестве синхронизируемого. это закрытый объект, предназначенный исключительтю для синхронизации.
Метод 51еер () намеренно вызывается для того, чтобы произошло переключение задач, хотя в данном случае это невозможно. Код в методе Ппш1с () заблокирован, и поэтому он может быть одновременно использован только в одном потоке. Таким образом, когда начинает выполняться второй порожденный поток, он не сможет войти в метод Ппшгс () до тех пор, пока из него не выйдет первый порожденный поток.
Благодаря этому гарантируется получение правильного результата. Для того чтобы полностью уяснить принцип действия блокировки, попробуйте удалить из рассматриваемой здесь программы тело метода 5пщ1с () . В итоге метод Ппщ1с () 806 Часть П. Библиотека С№ перестанет быть синхронизированным, а следовательно, он может параллельно использоваться в любом числе потоков для одного и того же объекта. Поскольку текущая сумма сохраняется в поле зпщ, она может быть изменена в каждом потоке, вызывающем метод янщ1с () . Это означает, что если два потока одновременно вызывают метод Бпщ1с () для одного и того же объекта, то конечный результат получается неверным, поскольку содержимое поля зищ отражает смешанный результат суммирования в обоих потоках.
В качестве примера ниже приведен результат выполнения рассматриваемой здесь программы после снятия блокировки с метода Пищтг () . Потомок №1 равна 1 Потомок №2 равна 29 Как следует из приведенного выше результата, в обоих порожденных потоках метод зищ1с () используется одновременно для одного и того же объекта, а это приводит к искажению значения в поле зпщ.
Ниже подведены краткие итоги действия блокировки. ° Если блокировка любого заданного объекта получена в одном потоке, то после блокировки объекта она не может быть получена в другом потоке. ° Остальным потокам, пытающимся получить блокировку того же самого объекта, придется ждать до тех пор, пока объект не окажется в разблокированном состоянии. ° Когда поток выходит из заблокированного фрагмента кода, соответствующий объект разблокируется. Другой подход к синхронизации потоков Несмотря на всю простоту и эффективность блокировки кода метода, как показано в приведенном выше примере, такое средство синхронизации оказывается пригодным далеко не всегда. Допустим, что требуется синхронизировать доступ к методу класса, который был создан кем-то другим и сам не синхронизирован.
Подобная ситуация вполне возможна при использовании чужого класса, исходный код которого недоступен. В этом случае оператор 1осх нельзя ввести в соответствующий метод чужого класса. Как же тогда синхронизировать объект такого класса? К счастью, этот вопрос разрешается довольно просто: доступ к объекту может быть заблокирован из внешнего кода по отношению к данному объекту, для чего достаточно указать этот объект в операторе 1ос)г.
В качестве примера ниже приведен другой вариант реализации предыдущей программы. Обратите внимание на то, что код в методе Пищтс () уже не является заблокированным, а объ- Потомок №1 начат. Текущая сумма для потока Потомок №2 начат. Текущая сумма ддя потока Текущая сумма для потока Текущая сумма для потока Текущая сумма для потока Текущая сумма для потока Текущая сумма для потока Текущая сумма ддя потока Текущая сумма для потока Текущая сумма для потока Сумма ддя потока Потомок Потомок №1 завершен.
Текущая сумма ддя потока Потомок №2 завершен. Потомок №2 равна 1 Потомок №1 равна 3 Потомок №2 равна 5 Потомок №1 равна 8 Потомок №2 равна 11 Потомок 91 равна 15 Потомок №2 равна 19 Потомок №1 равна 24 Потомок $2 равна 29 41 равна 29 Глава 23. Многопоточное программирование 807 ект 1оскоп больше не объявляется. Вместо этого вызовы метода яив1г () блокируются в классе МуТЬгеаб.
// Другой способ блокировэи для синхронизации доступа к объекту. ия1по Яузгепы ия1по Яуясев.ТЬгеабгпдг с1азз Яивкггау ( ъпг яипи риЬ11с Тпс 5ив1Г(ьпс[] пива) ( зив = ог // установить исходное значение суымы гог(ьпг 1=0г г < пиве.ьепдспг 1++) ( яив += пива[1]г Сопяо1е.нг1геъ1пе("Текушая сумма для потока " ч Тпгеад.сиггепстпгеаб.каше + " равна " + зив)) ТЬгеаб.51еер(10)) // разрешить переключение задач ) гесигп яппи ) с1азя МуТЬгеаб ( РиЬ11с ТЬгеаб ТЬгб) 1пс(] а; 1пс апянег) /* Создать один объект типа Яиваггау для всех экземпляров класса Мутпгеаб. */ зсасьс Яиваггау за = пен 5итдггау()г // Сконструировать новый поток.