Г. Шилдт - С#4.0 Полное руководство (1160795), страница 170
Текст из файла (страница 170)
В действительности синхронизация объектов во многих программах на С(( происходит практически незаметно. Ниже приведена общая форма блокировки: 1осй (1ос)гОЬ№) ( синхронизируемые операторы ) где 1ос)сОЬт' обозначает ссылку на синхронизируемый объект. Если же требуется синхронизировать только один оператор, то фигурные скобки не нужны. Оператор 1ос)с гарантирует, что фрагмент кода, защищенный блокировкой для данного объекта, будет использоваться только в потоке, получающем эту блокировку.
А все остальные потоки блокируются до тех пор, пока блокировка не будет снята. Блокировка снимается по завершении защищаемого ею фрагмента кода. Блокируемым считается такой объект, который представляет синхронизируемый ресурс. В некоторых случаях им оказывается экземпляр самого ресурса или же произвольный экземпляр объекта, используемого для синхронизации. Следует, однако, иметь в виду, что блокируемый объект не должен быть общедоступным, так как в противном случае он может быть заблокирован из другого, неконтролируемого в программе фрагмента кода и в дальнейшем вообще не разблокируется. В прошлом для блокировки объектов очень часто применялась конструкция 1ос)ь (ЬЬ1Б ) .
Но она пригодна только в том случае, если Спьз является ссылкой на закрытый объект. В связи с возможными программными и концептуальными ошибками, к которым может привести конструкция 1оск (ЬЬ1Б ), применять ее больше не рекомендуется. Вместо нее лучше создать закрытый объект, чтобы затем заблокировать его. Именно такой подход принят в примерах программ, приведенных далее в этой главе.
Но в унаследованном коде С(( могут быть обнаружены примеры применения конструкции 1осх (ЬЬзв) . В одних случаях такой код оказывается безопасным, а в других — требует изменений во избежание серьезных осложнений при его выполнении. В приведенной ниже программе синхронизация демонстрируется на примере управления доступом к методу Бцы1С (), суммирующему элементы целочисленного массива. /! Использовать блокировку для синхронизации доступа к объекту. цвгпо Бувдепн цвъпд Бувсез.тьгеаб1пчг с1авв Бцзйггау ( ьпс воз~ оЬбесд 1оскбп = пен оЬ)есв() О закрытый объект, доступный для последукпгей блокировки рцЬ1гс ъпс Бцз1С(1пс() пцзв) Глава 23.
Многопоточное программирование. Часта первая: основы 851 1ос)с(1ос)сбп) ( // заблокировать весь метод яшп = 0; О установить исходное значение сумыы Гог(апС г=о; 1 < пива.ЬепяСЛг 1++) ( яшп ее пива[11; Сопяо1е.нгсгегапе("Текушая сумма для потока Тпгеас(.сиггепСТЬгеас).Наше + " равна " + яшп)Г Тнгеаи.Б1еер(10)т О разрешить переклссчение задач ) гесигп вшп; ) ) с1аяв Иутпгеаи ( ри)п11с Тигеаи Тлгит ).пс () а; 1пс апянег; // Создать один объект типа Бшппггау для всех // экземпляров класса Мутлгеас).
ясас1с Бивлггау яа = пен Бипслггау(); // Сконструировать новый поток. ри)п11с Мутнгеаи(ягг1пч паве, 1пС() пива) ( а = пшпя; Тасс) = лен Тлгеаи(с)т1я.пип)т Тасс).наше = паве; Тпги.БСагг(); // начать поток // Начать выполнение нового потока. тога нип() ( Сопяо1е.нг1ге11пе(Т)сгс(.наше + " начат."); апвнег = яа.Яив1С(а) сопво1е.хг1сеп1пе("сумма для потока " + тлги.наве т " равна " + апянег) Сопяо1е.Хгасет Тле(ТПги.паве + " завершен."); ) с1аяя Бупс ( яСаСТс чоса Ма1п() ТпС(1 а = (1, 2, 3, 4, 51) мутьгеаи вс1 = лен мутлгеас)("потомок ()1", а) с Мутпгеаб вС2 = пен МуТНгеаб("Потомок ()2", а); вС1.ТЛгс1.,То1п (): вс2.тпги.со1п()т ) ) 852 Часть!). Библиотека С() Ниже приведен результат выполнения данной программы, хотя у вас он может оказаться несколько иным.
Потомок ()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.
Рассмотрим эту программу более подробно. Сначала в ней создаются три класса. Первым из них оказывается класс Яшпдггау, в котором определяется метод Бпщ10 (), суммирующий элементы целочисленного массива. Вторым создается класс Мутпгеас), в котором используется статический объект ва типа Бощйггау. Следовательно, единственный объект типа Бощ)(ггау используется всеми объектами типа мутпгеась С помощью этого объекта получается сумма элементов целочисленного массива. Обратите внимание на то, что текущая сумма запоминается в поле зощ объекта типа БощАггау. Поэтому если метод Яощ10 () используется параллельно в двух потоках, то оба потока попытаются обратиться к полю зощ, чтобы сохранить в нем текущую сумму. А поскольку это может привести к ошибкам, то доступ к методу Яощ10 () должен быть синхронизирован.
И наконец, в третьем классе, Я упс, создаются два потока, в которых подсчитывается сумма элементов целочисленного массива. Оператор 1ос)г в методе Бищ1С () препятствует одновременному использованию данного метода в разных потоках. Обратите внимание на то, что в операторе 1ос к объект 1ос)озп используется в качестве синхронизируемого. Это закрытый объект, предназначенный исключительно для синхронизации. Метод Б1еер () намеренно вызывается для того, чтобы произошло переключение задач, хотя в данном случае это невозможно. Код в методе Бищ10 () заблокирован, и поэтому он может быль одновременно использован только в одном потоке.
Таким образом, когда начинает выполняться второй порожденный поток, он не сможет войти в метод Бощ10 ( ) до тех пор, пока из него не выйдет первый порожденный поток. Благодаря этому гарантируется получение правильного результата. Для того чтобы полностью уяснить принцип действия блокировки, попробуйте удалить из рассматриваемой здесь программы тело метода Яощ10 () . В итоге метод Яищ10 () перестанет быть синхронизированным, а следовательно, он может параллельно использоваться в любом числе потоков для одного и того же объекта.
Поскольку текущая сумма сохраняется в поле знщ, она может быть изменена в каждом потоке, вызывающем метод Бощ1С ( ) . Это означает, что если два потока одновременно вызывают метод Яощ10 () для одного и того же объекта, го конечный результат получается Глава зз. Многопоточное программирование. Часть первая: основы 883 неверным, поскольку содержимое поля ацщ отражает смешанный результат сумми- рования в обоих потоках. В качестве примера ниже приведен результат выполнения рассматриваемой здесь программы после снятия блокировки с метода Бцш1С () .
Потомок 41 равна 1 Потомок 42 равна 29 Как следует из приведенного выше результата, в обоих порожденных потоках метод Бцш1С () используется одновременно для одного и того же объекта, а это приводит к искажению значения в поле ацщ. Ниже подведены краткие итоги использования блокировки.
° Если блокировка любого заданного обьекта получена в одном потоке, то после блокировки объекта она не может быть получена в другом потоке. ° Остальным потокам, пытающимся получить блокировку того же самого объекта, придется ждать до тех пор, пока объект не окажется в разблокированном СОСТОЯНИИ. ° Когда поток выходит из заблокированного фрагмента кода, соответствующий объект разблокируется.
Другой подход к синхронизации потоков Несмотря на всю простоту и эффективность блокировки кода метода, как показано в приведенном выше примере, такое средство синхронизации оказывается пригодным далеко не всегда. Допустим, что требуется синхронизировать доступ к методу класса, который был создан кем-то другим и сам не синхронизирован. Подобная ситуация вполне возможна при использовании чужого класса, исходный код которого недоступен. В этом случае оператор 1ос Х нельзя ввести в соответствующий метод чужого класса. Как же тогда синхронизировать объект такого класса? К счастью, этот вопрос разрешается довольно просто: доступ к объекту может быть заблокирован из внешнего кода по отношению к данному объекту, для чего достаточно указать этот объект в операторе 1осК.
В качестве примера ниже приведен другой вариант реализации предыдущей программы. Обратите внимание на то, что код в методе Пцщ1С () уже не является заблокированным, а объект 1осх()п больше не объявляется. Вместо этого вызовы метода эпщ1С () блокируются в классе МуТ)гсеас(. !/ Другой способ блокировки для синхронизации доступа к обьекту. ца1пч 5уасешг Потомок 41 начат. Текущая. сумма для потока Потомок №2 начат. Текущая оуьма лля потока Текущая сумма для потока Текущая сумма для потока Текущая сумма для потока Текущая сумма для потока Текущая сумма для потока Текущая сумма для потока Текущая сумма для потока Текупщя сумма для потока сумма дпя потока Потомок Потомок 41 завершен.
Теку|лая сумма для потока Потомок 42 завершен. Потомок 42 Потомок №1 Потомок №2 Потомок 41 Потомок №2 Потомок 41 Потомок №2 Потомок 41 Потомок №2 41 равна 2 равна 1 равна 3 равна 5 равна 8 равна 11 равна 15 равна 19 равна 24 равна 29 9 854 Часть )1. Бибаиотеиа С№ пя1по Буяпеш.ТЬгеабупСЫ с1аяя 5пшаггау ( 1пг яош) роо11с ьпо Бош1Г(упс(! поше) ( яшп = 0; У! установить исходное значение суммы Тот(ьпс 1=0; 1 < ппшя.Белусов] г++) ( поше(1]; сопяо1е.иггсеььпе("текущая сумма длп потока " + Тьгеаб.счггепстьгеаб.Маше + " равна " + япш); ТЬгеаб.51еер(10)] // разрешить переклгчение задач ) гегогп яппи ) ) с1аяя Мутьгеаб ( роЬ11с Тьгеаб Тйгб] ьпг(] а; Тпо апвиег; /* Создать один объект типа Бошдггау для всех экземпляров класса Мутогеаб.
*У япап1с Бпп|Аггау яа = пен БшпАггау(); Сконструировать новьпт поток. роо11с Мутьгеаб(ясгупд паше, Тпп() пшпя) а = пшпя; Тьгб = пен Тьгеаб(ГН1я.йоп); Тагб.наше = паве; Тогб.БГагп()] У/ начать поток ) уу Начать выполнение нового потока. чояб Впп() Сопяо1е.йгуседьпе(ТЬгб.Наше + " начат."); Заблокировать вызовы метода Бпш1Г() 1ось(яа) апянег = яа.Бош1Г(а); сопяо1е.хг1сеьупе("сумма длн потока " + т)]гб.маше + " равна " е апянег); Сопяо1е.иг11есьпе(ТЬгб.Маше + " завершен."); ) ) с1аяя Бупс ( ясапьс чоуб Мауп() ( Тпп(] а= (1, 2, 3, 4, Б); Мутьгеаб шп1 = пен Мутьгеаб("Потомок $1", а); Мутьгеаб шс2 = пен Мутьгеаб("Потомок 42", а); шг1.ТНгб.то1п(): шь2.
ТЬгб. Юсуп () ' ) ) Глава 23. Многопоточное программирование. Часть первая: основы 855 В данной программе блокируется вызов метода за. Зшп1С (), а не сам метод Зпгп1Ь () . Ниже приведена соответствующая строка кода, в которой осуществляется подобная блокировка. Заблокировать вызовы метода Явз1Г() 1оск(ва) апвиег = ва.звы1Г(а); Объект ва является закрытым, и поэтому он может быть благополучно заблокирован. При таком подходе к синхронизации потоков данная программа дает такой же правильный результат, как и при первоначальном подходе.