Глубины Indy. 2. Техническая поддержка
Скачать 1.03 Mb.
|
15.2.4. Явная защита (Explicit Protection) Явная защита заставляет каждую задачу знать, что ресурсы защищены и требуют явных шагов по доступу к ресурсу. Обычно такой код расположен в отдельной подпрограмме, которая используется многими задачами конкурентно и работает как потоко-безопасная обертка. Явная защита обычно использует для синхронизации доступа к ресурсу специальный объект. Вкратце, защита ресурса ограничивает доступ к ресурсу для одной задачи за раз. Защита ресурса не ограничивает доступ к ресурсу, это требует знания специфики каждого ресурса. Вместо этого она подобна сигналам движения и код адаптирован так чтобы следовать и управлять сигналам трафика. Каждый объект защиты ресурса реализует определенный тип сигнала, используя различную логику управления и налагая различные ограничения на производительность . Это позволяет выбрать подходяший объект защиты в зависимости от ситуации. Существует несколько типов объектов защиты ресурсов и они будут отдельно рассмотрены позже. 15.2.4.1. Критические секции (Critical Sections) Критические секции могут быть использованы для управления доступом к глобальным ресурсам. Критические секции требуют мало ресурсов и реализованы в VCL как TCriticalSection. Вкратце, критические секции позволяют отдельному потоку в многопоточном приложении, временно блокировать доступ к ресурсу для других потоков исаользующих эту же критическую секцию. Критические секции подобны светофорам, которые зажинают зеленый сигнал только если впереди нет ни одной другой машины. Критические секции могут быть использованы, чтобы быть уверенным, что только один поток может исполняться в одно время. Поэтому, защищенные блоки должны быть минимального размера, так как они могут сильно понизить производительность, если будут неправильно использованы. Поэтому, каждый уникальный блок должен также использовать свою собственную критическую секцию (TCriticalSection) вместо использования единой для всего приложения. Для входа в критическую секцию вызовите метод Enter и затем метод Leave для выхода из секции. Класс TCriticalSection также имеет методы Acquire и Release, которые аналогичны Enter и Leave соответственно. Предположим, что сервер должен вести лог клиентов и отображать информацию в главном потоке. Мнение, что это должно быть синхронизироваться. Тем не менее, 66 использование данного метода может негативно сказаться на производительности потока соединения, если много клиентов будут подключаться одновременно. В зависимости от потребностей сервера, лучшим решением был бы лог информации, а главный поток мог бы читать ее по таймеру. Следующий код является примером данной техники, которая использует критические секции. var GLogCS: TCriticalSection; GUserLog: TStringList; procedure TformMain.IdTCPServer1Connect(AThread: TIdPeerThread); var s: string ; begin // Username s := ReadLn; GLogCS.Enter; try GUserLog.Add( 'User logged in: ' + s); finally GLogCS.Leave; end ; end ; procedure TformMain.Timer1Timer(Sender: TObject); begin GLogCS.Enter; try listbox1.Items.AddStrings(GUserLog); GUserLog.Clear; finally GLogCS.Leave; end ; end ; initialization GLogCS := TCriticalSection.Create; GUserLog := TStringList.Create; finalization FreeAndNil(GUserLog); FreeAndNil(GLogCS); end В событии Connect имя пользователя читается во временную переменную перед входом в критическую секцию. Это сделано, чтобы избежать блокирования кода низкоскоростным клиентом в критической секции. Это позволяет сетевому соединению быть выполненным до входа в критическую секцию. Для сохранения максимальной производительности, код в критической секции сделан минимально возможным. Событие Timer1Timer возбуждается в главной форме. Интервал таймера может быть короче для более частых обновлений, но потенциально может замедлить восприятия соединения. Если требуется выполнять логирование и в других местах, кроме регистрации пользователей, то существует большая вероятность появления узкого места в производительности. Больший интервал обновлений, сводит задержки в интерфейсе к минимуму. Конечно, многие серверы не имеют никакого интерфейса, а те в которых он есть, он является вторичным и выполняется с меньгим приоритетом, чем поток, обслуживающий клиентов, что вполне допустимо. 67 Примечание пользователям Delphi 4: Класс TCriticalSection находится в модуле SyncObjs. Модуль SyncObjs обычно не включен в Delphi 4 Standard Edition. Если Вы используете Delphi 4, то Вы можете загрущить SyncObjs.pas файл с web сайта Indy. Этот файл не срдержит всей функциональности реализованной Борланд, но имеет реализацию класса TCriticalSection. 15.2.4.2. Класс TMultiReadExclusiveWriteSynchronizer (TMREWS) В предыдущем примере, Класс TCriticalSection был использован для защиты доступа к глобальным данным. Он нужен случаях когда глобальные данные всегда обновляются. Конечно, если глобальные данные должны быть доступны в режиме чтения и только иногда для записи, то использования класса TMultiReadExclusiveWriteSynchronizer будет более эффективно. Класс TMultiReadExclusiveWriteSynchronizer имеет очень длинное и трудно читаемое имя. Поэтому мы будем называть его просто TMREWS. Преимущества использования TMREWS состоит в том, что он позволяет конкурентное чтение из многих потоков, и действует как критическая секция, позволяя только одному потку доступ для записи. Недостатком TMREWS является, что он более сложен в использовании. Вместо Enter/Acquire и Leave/Release, TMREWS имеет методы: BeginRead, EndRead, BeginWrite и EndWrite. 15.2.4.2.1. Специальное примечание к классу TMREWS До Delphi 6 в классе TMultiReadExclusiveWriteSynchronizer имелась проблема, приводящая к взаимному блокированию (dead lock) при повышении уровня блокировки с чтения на запись. Поэтому, вы не никогда должны использовать данную возможность изменения блокировки чтения в блокировку записи, несмотря на то, что документация утверждает что это можно сделать. Если вам нужна подобная функциональность, то имеется обходной путь. Он состоит в том, что сначала надо освободить блокировку чтения, а затем поставить блокировку записи. Тем не менее, если вы установили блокировку записи, то вы должны затем снова проверить условие, необходимое для начала записи. Если оно все еще выполняется, делаете свою запись, в противном случае немедленно снимите блокировку. Класс TMultiReadExclusiveWriteSynchronizer так требует особой осторожности при использовании в Delphi 6. Все версии класса TMultiReadExclusiveWriteSynchronizer включая, поставляемый в update pack 1 и в update pack 2 имеют серьезные проблемы, которые могут вызвать взаимную блокировку. Обходных путей пока нет. Borland в курсе этого и выпустил неофициальный патч и также ожидаются официальные патчи. 15.2.4.2.2. Примечание к классу TMREWS в Kylix Класс TMultiReadExclusiveWriteSynchronizer в Kylix 1 и Kylix 2 реализован с помощью критических секций и не имеет преимуществ перед критическими секциями. Это сделано, что бы можно было писать общий код и для Linux и для Windows. 68 В будущих версиях Kylix, класс TMultiReadExclusiveWriteSynchronizer вероятно будет изменен, что бы работал также как Windows. 15.2.4.3. Выбор между Critical Sections и TMREWS Поскольку класс TMREWS имеет некоторые проблемы, мой совет просто избегать его. Если вы решили использовать его, вы должны быть уверены, что это действительно лучший выбор и он должен быть реализован с помощью пропатченной версии, не подверденной зависаниям. Правильное использование TCriticalSection в большинстве случаев дает обычно такой же быстрый синхронизированный доступ, а в некоторых случаях самый. Научитесь правильно использовать TCriticalSection, так как неправильное исаольщование может иметь негативное влияние на производительность. Ключом к защите любых ресурсов является использование множества точек входа в секции и выполнение критических секций кода как можно быстрее. Когда проблема может быть разрешена с помощью критических секций, то она должна решаться с их помощью, вместо использования TMREWS, поскольку критические секции проще и быстрее. В общем, всегда используйте критические секции вместо TMREWS. Класс TMREWS работает лучше, если встретятся следующие условия: 1. доступ осуществляется по чтению и записи. 2. преобладает доступ по чтению. 3. период блокировки велик и не может быть разбит на меньшие независимые куски. 4. доступен пропатченый класс TMREWS и известно что он работает корректно. 15.2.4.4. Сравнение производительности Как уже упоминалось ранее критические секции легковесны и мало влияют на производительность. Они реализованы в ядре операционной системы. ОС реализует их используя короткие эффективные ассемблерные команды. Класс TMREWS более сложен и поэтому больше влияет на производительность. Он должен управлять списком запросов для поддержания состояния блокировок. Для того чтобы продемонстрировать разницу был создан демонстрационный проект ConcurrencySpeed.dpr. Он проводит три простых замера: 1. TCriticalSection – Enter и Leave 2. TMREWS – BeginRead и EndRead 3. TMREWS – BeginWrite и EndWrite Он делает это выполняя цикл заданное количество раз. Для примера 100000. В моих тестах я получил следующие результаты. 1. TCriticalSection: 20 2. TMREWS (Read Lock): 150 3. TMREWS (Write Lock): 401 69 Конечно, результаты зависят от компьютера. Но важна разница, а не абсолютные числа. Я могу видеть что при оптимальных условиях запись TMREWS в 7.5 раз медленне критических секций. А запись медленнее в 20 раз. Нужно также заметить, что критические секции практически не деградирую при нагрузке, тогда как TMREWS сильно сдает. Тест выполнялся в простом цикле, и не было других запросов на блокировку. В реальной жизни TMREWS будет еще медленнее чем показано здесь. 15.2.4.5. Мьютексы (Mutexes) Функции мьютексов почти полностью аналогичны критическим секциям. Разница состоит в том, что мьютексы - это более мощная версия критических секций с большим количеством свойств и конечно в связи с этим большим воздействием на производительность. Мьютексы имеют дополнительные возможности по именованию, назначению атрибутов безопасности и они доступны между процессами. Мьютексы могут быть использованы для синхронизации потоков, но они редко используются в данном качестве. Мьютексы были разработаны и используются, для синхронизации между процессами. 15.2.4.6. Семафоры (Semaphores) Семафоры подобны мьютексам, но вместо единичного доступа, позволяют множественный. Количество доступов, которое разрешено определяется при создании семафора. Представим, что мьютекс это охранник в банке, который позволяет доступ только одному человеку к банкомату (ATM). Только один человек за раз может использовать его и охранник защищает машину от доступа нескольких человек одновременно. В данном случае, семафор будет более предпочтителен, если установлено четыре банкомата. В этом случае охранник позволяет нескольким людям войти в банк и использовать эти банкоматы, но не более четырех человек одновременно. 15.2.4.7. События (Events) События – это сигналы, которые могут быть использованы между потоками или процессами, для оповещения о том, что что-то произошло. События могут быть использованы для оповещения других задач, когда что-то произошло или требуется вмешательство. 15.2.5. Потоко-безопасные классы Потоко-безопасные классы были специально разработаны для защиты специфических типов ресурсов. Потоко-безопасные классы реализуют специфический тип ресурса и имеют сокровенные знания, что это за ресурс и как он функционирует. 70 Потоко-безопасные классы могут быть простыми, как потоко-безопасный integer или комплексными, как потоко-безопасные базы данных. Потоко-безопасные классы внутри используют потоко-безопасные объекты для выполнения своих функций. 15.2.6. Изоляция (Compartmentalization) Изоляция – это процесс изоляции данных и назначения их только для использования одной задачей. На серверах изоляция - это естественный путь, так как каждый клиент может обслуживаться выделенным потоком. Когда изоляция не является естественной, необходимо оценить возмодность ее использования . Изоляция часто может быть достигнута путем создания копии глобальных данных, работы с этими данными и затем возврата этих данных в глобальную область. При использовании изоляции, данные блокируются только во время инициализации и после окончания выполнения задачи, или во время применения пакетных обновлений. 16. Кодовые потоки Многопоточность страшит многих программистов и часто расстраивает новичков. Потоки это элегантный путь решения многих проблем и он, однажды освоенный станет ценных вложением в вашу копилку опыта. Тема потоков может потребовать написания отдельной книги. 16.1. Что такое поток? Поток это ваш код исполняемый паралелльно с другим вашим кодом в одной программе. Использование потоков позволяет выполнять несколько задач одновременно. Представим, что в вашей компании только один телефон. Поскольку, имеется только одна телефонная линия и только один человек может использовать ее в одно время. Тем не менее, если в установите несколько телефонных линий, то и другие смогут делать телефонные звонки не узнавая свободна линия или нет. Потоки позволяют вашему приложению делать несколько дел одновременно. Параллельное выполнение потоков доступно даже если у вас один процессор. В действительности только одни поток исполняется, но операционная система принудительно прерывает потоки и исполняет их по очереди. Каждый поток выполняется только в течение очень короткого интервала времени. Это позволяет делать десятки тысяч переключений в секунду. Поскольку, переключение, вытесняющее и непредсказуемое, то будет казаться, что программы выполняются параллельно и программное обеспечение должно быть предусмотрено для работы в таком режиме. Если процессоров несколько, то потоки реально могут выполняться параллельно, но все равно каждый процессор выполняет только один поток. 16.2. Достоинства потоков Использование потоков дает дополнительные преимущества перед обычным однопоточным дизайном. 16.2.1. Управление приоритетами (Prioritization) 71 Приоритеты отдельных потоков могут быть подстроены. Это позволяет отдельным серверным соединениям или клиентам получать больше процессорного времени. Если вы повышаете приоритет всех потоков, эффект не будет заметен, так как все они будут иметь равный приоритет. Тем не менее они могут отнять время у потоков других процессов. Вы должны осторожно относиться к этому и не устанавливать слишком высокий приоритет, поскольку это может вызвать конфликты с потоками обслуживающими ввод/вывод. В большинстве случае вместо повышения приоритета, вы можете уменьшать его. Это позволяет менее важным задачам не забирать слишком много времени от более важных задач. Управление приоритетами потока также очень полезно на серверах и в некоторых случаях вы можете пожелать управлять приоритетом на основе логина. Если администратор подключается, то вы можете пожелать увеличить его приоритет, по сравнению с другими пользователями. 16.2.2. Инкапсуляция Использование потоков позволяет отделить каждую задачу от других, чтобы они меньше влияли друг на друга. Если вы использовали Windows 3.1, то вы вероятно помните, как одно плохое приложение могло легко остановить всю систему. Потоки предотвращают подобное. Без потоков, все задачи должны были реализрваться в одном учвастке кода, создавая дополнительные сложности. С потоками, каждая задача может быть разделена на независимые секции, делая ваш код проще для программирования, когда требуется одновременное выполнение задач. 16.2.3. Безопасность Каждый поток может иметь свои собственные атрибуты безопасности, базируясь на аутентификации или других критериях. Это особенно полезно для серверных реализаций, где каждый пользователь имеет поток, ассоциированный с его соединением. Это позволяет операционной системе, реализовать должную безопасность для каждого пользователя, ограничивая его доступ к файлам и другим системным объектам. Без этого свойства вы должны бы реализовывать безопасность, возможно оставляя дыры в безопасности. 16.2.4. Несколько процессоров Потоки могут автоматически использовать множество процессоров если они доступны. Если потоки не используются в вашем приложении, то вы имеете только один главный кодовый поток. Поток может исполняться только на одном процессоре и поэтому ваше приложение не исполняется максимально быстро. Другие процессы могут использовать другие процессоры, так же как и операционная система. Вызовы сделанные к операционной системе вашим приложением внутренне многопоточны и ваше приложение все равно получает определенное ускорение. В дополнение, время исполнения вашего приложение может лучше, поскольку и другие процессоры задействованы для других приложений и меньше нагружают ваш процессор. 72 Наилучший путь получить преимущества от нескольких процессоров, это использование нескольких потоков в вашем приложении. Это не только позволяет вашему приложению использовать несколько процессоров, но и дает больше процессорного времени вашему приложению, поскольку у вас больше потоков. 16.2.5. Не нужна последовательность Потоки предоставляют подлинное параллельное выполнение. Без потоков все запросы должны выполняться в одном потоке. Для этого работа каждой задачи должна быть разделена на малые части которые можно быстро выполнить. Если любая часть блокируется или требует много времени для исполнения, то все остальные части должны быть задержаны, пока она не выполнится. После того как одна части выполнится, начинается выполнение другой и так далее. С потоками, каждая задача может быть закодирована отдельно и операционная система разделит процессорное время между этими частями. |