Многопоточность страшит почти всех программистов и нередко, в конце концов, расстраивает новичков. Потоки это стильный путь решения почти всех заморочек и он, в один прекрасный момент освоенный так сказать станет ценных вложением в вашу копилку опыта. Тема потоков может так сказать востребовать написания отдельной книжки.
16.1. Что такое поток?
Поток это ваш код исполняемый параллельно с иным вашим кодом в одной програмке. Внедрение потоков, мягко говоря, дозволяет делать несколько задач сразу.
Представим, что в вашей компании лишь один телефон. Так как, имеется лишь одна, как заведено, телефонная линия и лишь один человек может употреблять ее в одно время. Тем более, ежели в установите несколько телефонных линий, то и остальные сумеют делать, как все говорят, телефонные звонки не, как мы привыкли говорить, узнавая свободна линия либо так сказать нет. Потоки разрешают вашему приложению делать несколько дел сразу.
Параллельное выполнение потоков доступно даже ежели у вас один процессор. В реальности лишь одни поток так сказать исполняется, но операционная система принудительно прерывает потоки и исполняет их по очереди. Каждый поток выполняется лишь в течение чрезвычайно, как мы выражаемся, недлинного интервала времени. Это, в конце концов, дозволяет делать 10-ки тыщ переключений в секунду. Так как, переключение, вытесняющее и непредсказуемое, то будет, вообщем то, казаться, что программы как бы выполняются параллельно и программное обеспечение обязано быть также предвидено для работы в таком режиме.
Ежели процессоров несколько, то потоки реально могут выполняться параллельно, но все равно каждый процессор наконец-то выполняет лишь один поток.
16.2. Плюсы потоков
Внедрение потоков дает доп достоинства перед обыденным однопоточным дизайном.
16.2.1. Управление ценностями (Prioritization)
Ценности отдельных потоков могут быть подстроены. Это, вообщем то, дозволяет отдельным, как всем известно, серверным соединениям либо клиентам получать больше процессорного времени. Ежели вы увеличиваете наконец-то ценность всех потоков, эффект не будет приметен, потому что они все как раз будут иметь равный наконец-то ценность. Тем более они могут отнять время у потоков остальных действий. Вы должны осторожно относиться к этому не устанавливать очень высокий, мягко говоря, ценность, так как это может вызвать конфликты с потоками обслуживающими ввод/вывод.
В большинстве случае заместо увеличения приоритета, вы сможете уменьшать его. Это, стало быть, дозволяет наименее принципиальным задачкам не забирать очень, как всем известно, много времени от наиболее принципиальных задач.
Управление ценностями потока также чрезвычайно полезно на серверах и в неких вариантах вы сможете пожелать управлять ценностью на базе логина. Ежели админ подключается, то вы сможете, в конце концов, пожелать прирастить его, стало быть, ценность, по сопоставлению с иными юзерами.
16.2.2. Инкапсуляция
Внедрение потоков дозволяет наконец-то отделить каждую задачку от остальных, чтоб они меньше влияли друг на друга.
Ежели вы употребляли Windows 3.1, то вы возможно помните, как одно нехорошее приложение могло просто, в конце концов, приостановить всю систему. Потоки предотвращают схожее. Без потоков, все задачки должны были, наконец, реализрваться в одном учвастке кода, создавая доп трудности. С потоками, любая задачка как раз быть может разбита на независящие секции, делая ваш код проще для программирования, когда требуется одновременное выполнение задач.
16.2.3. Сохранность
Каждый поток может, стало быть, иметь свои собственные атрибуты сохранности, базируясь на аутентификации либо остальных аспектах. Это в особенности полезно для серверных реализаций, где каждый юзер имеет поток, ассоциированный с его соединением. Это дозволяет операционной системе, воплотить должную сохранность для каждого юзера, ограничивая его доступ к файлам и иным, как все знают, системным объектам. Без этого характеристики вы должны бы как раз реализовывать сохранность, может быть оставляя дыры в сохранности.
16.2.4. Несколько процессоров
Потоки, наконец, могут автоматом употреблять множество процессоров ежели они доступны . Ежели потоки не употребляются в вашем приложении, то вы имеете лишь один основной кодовый поток. Поток может исполняться лишь на одном процессоре и потому ваше приложение не исполняется очень быстро.
Остальные процессы, стало быть, могут употреблять остальные процессоры, так же как и операционная система. Вызовы, как мы выражаемся, изготовленные к операционной системе вашим приложением внутренне многопоточны и ваше приложение все равно, в конце концов, получает определенное ускорение. В дополнение, время выполнения вашего приложение, в конце концов, может лучше, так как и остальные процессоры задействованы для остальных приложений и меньше нагружают ваш процессор. Лучший путь получить достоинства от пары процессоров, это внедрение пары потоков в вашем приложении. Это не только лишь дозволяет вашему приложению применять несколько процессоров, да и дает больше процессорного времени вашему приложению, так как у вас больше потоков.
16.2.5. Не нужна последовательность
Потоки предоставляют подлинное параллельное выполнение. Без потоков все запросы должны выполняться в одном потоке. Для этого работа, как мы с вами постоянно говорим, каждой задачки обязана быть, вообщем то, разбита на малые части которые можно быстро как бы выполнить. Ежели, как все говорят, неважно какая часть блокируется либо также просит много времени для выполнения, то все другие части должны быть задержаны, пока она не выполнится. После, как все знают, того как одна части выполнится, начинается выполнение иной и т.д..
С потоками, любая задачка быть может закодирована раздельно и операционная система, вообщем то, поделит процессорное время меж этими частями.
16.3. Процессы против потоков
Процессы различаются от потоков, но их нередко как бы путают. Процесс это вполне законченный экземпляр приложения, который просит ресурсов для начала выполнения, включая передачу управления операционной системе и распределение доборной памяти. Преимущество действий состоит в том, что они на сто процентов изолированы один от другого, тогда как потоки изолированы лишь отчасти. Ежели процесс падает, то все другие процесс, вообщем то, остаются в неприкосновенности.
16.4. Потоки против действий
Потоки подобны действиям и являются параллельно, как мы привыкли говорить, выполняемым кодом. Но потоки, в конце концов, являются частью, как люди привыкли выражаться, родительского процесса. Каждый поток так сказать имеет собственный собственные стек, но употребляет совместную куча совместно с иными потоками, такого же процесса. Потоки скорее, стало быть, создавать, чем процесс. Потоки также наконец-то делают наименьшую нагрузку на операционную систему и требуют меньше памяти для работы.
Так как потоки не на сто процентов изолированы друг от друга, то их взаимодействие вместе существенно проще.
16.5. Переменные потоков
Переменные потока, мягко говоря, объявляются с внедрение, как мы выражаемся, ключевика ThreadVar.
Переменные потока подобны глобальным переменным и, мягко говоря, объявляются схожим образом. Различие в том, что, как мы выражаемся, рядовая глобальная переменная является, как большая часть из нас постоянно говорит, глобальной для всех потоков, а, как мы выражаемся, переменные потока специфичны для, как многие думают, каждого потока. Так в каждом потоке, возникает свое собственное глобальное место.
Переменные потока могут быть полезны, когда тяжело так сказать передавать ссылки на объект меж библиотеками либо изолированными кусочками кода. Естественно переменные потока имеют и ограничения. Переменные потока не могут быть применены либо объявлены снутри пакетов. Везде где лишь может быть, должны быть применены, как мы выражаемся, члены-переменные в классе потока. Они обеспечивают как бы наименьшую нагрузку и доступны для использования в пакетах.
16.6. Определения потоковый (threadable) и потоко-безопасный (threadsafe)
Термин потоко-безопасный (threadsafe) нередко употребляется либо трактуется некорректно. Его нередко используют сразу к потоковый (threadable) и потоко-безопасный (threadsafe), что приводит к ошибкам. В данном тексте, определения потоковый и потоко-безопасный точно, вообщем то, определены и имеют различное значение.
16.6.1. Термин потоковый (threadable)
Термин потоковый значит, что объект может употребляться снутри потока либо употребляться потоком, ежели он подабающим образом защищен. Объект помечается как потоковый и традиционно не, наконец, имеет зависимости от потоков.
Ежели объект потоковый, то это значит, что он, мягко говоря, может употребляться в один потоке в каждый момент времени. Это как раз быть может обеспечено созданием его локально в потоке либо через глобальную защиту ресурсов.
Примеры, как заведено выражаться, потоковых переменных – это значения типа Integer, String и остальные ординарные типы, TList, TStringList и большая часть не, как большинство из нас привыкло говорить, зрительных классов.
Объект может так сказать иметь доступ к, как всем известно, глобальным переменным либо элементам управления GUI. Неограниченное (а нередко не необходимое) внедрение глобальных переменных в большинстве вариантах, это то что мешает сделать составляющие как бы потоковыми.
Объект быть может, как люди привыкли выражаться, библиотекой, компонентом либо, как всем известно, процедурой.
16.6.2. Термин потоко-безопасный (threadsafe)
Термин потоко-безопасный значит, что объект осведомлен о потоках и имеет внутреннюю свою защиту ресурсов. Потоко-безопасные значения объекты как бы могут употребляться в одном либо пары потоках без внедрения защиты ресурсов.
Примером потоко-безопасных классов является VCL класс TThreadList, также потоко-безопасные классы Indy. Естественно как бы операционная система также потоко-безопасна.
16.7. Синхронизация
Синхронизация – это процесс передачи инфы из вторичного потока, как люди привыкли выражаться, основному. VCL поддерживает это при помощи способа Sychronize класса TThread.
16.8. Класс TThread
Класс TThread это класс реализации потоков, который включен в VCL и, стало быть, предоставляет, как мы с вами постоянно говорим, хорошую базу для построения потоков.
Для реализация потока класс наследуется от TThread и переписывается способ Execute.
16.9. Компонент TThreadList
Компонент TThreadList – это потоко-безопасная реализация класса TList. Класс TList быть может применен в любом количестве потоков без необходимости защиты от одновременного доступа.
Класс TThreadList работает подобно TList, но не совершенно также. Некие способы, такие как as Add, Clear и Remove подобны. Для остальных операций, класс the TThreadList должен быть заблокирован при помощи способа LockList. способ LockList – это функции и она возвращает ссылку на внутренний экземпляр класса TList. Когда он заблокирован, все остальные потоки будут заблокированы. По этому, чрезвычайно принципиально разблокировать (Unlock) как можно скорее.
Пример операций с TThreadList:
with MyThreadList.LockList do try t := Bytes div {/} KILOry for i := 0 to Count - 1 do begin // Operate on list items Items[i] := Uppercase(Items[i]); end; finally MyThreadList.UnlockList; end;
Чрезвычайно принципиально, что бы перечень постоянно был разблокирован по окончанию кода и потому постоянно блокирование и разблокирование должны делаться в защитном блоке try..finally. Ежели перечень остается заблокированным, то это приведет к зависанию остальных потоков при попытке их доступа к списку.
16.10. Indy
Indy содержит, как большая часть из нас постоянно говорит, много доп классов, которые дополняют VCL способностями поддержки потоков. Данные классы в реальности независимы от ядра Indy и полезны и для всех приложений. Они есть в Indy, так как Indy разработана для работы с потоками. Indy не только лишь употребляет эти классы для как бы серверных реализаций, да и предоставляет их разрабу. Данная глава предоставляет лаконичный обзор этих классов.
16.11. Компонент TIdThread
Компонент TIdThread – это наследник от TThread и как бы добавляет доп расширенные характеристики, большая часть из которых созданы для построения серверов и также предоставляет поддержку пулов потоков и повторного использования.
Ежели вы знакомы с потоками VCL, то чрезвычайно принципиально принять во внимание, что TIdThread резко, мягко говоря, различается в пары как бы главных областях. В TThread, способ Execute должен быть перегружен в наследниках, но в TIdThread должен быть перегружен способ Run. Ни в коем случаен не перегружайте способ Execute класса TIdThread, потому что это будет как бы препятствовать внутренним операциям TIdThread.
Для всех наследников TIdThread, способ Run должен быть перегружен. Когда поток становится, как люди привыкли выражаться, активным, выполняется способ Run. Способ Run поочередно вызывается в TIdThread, пока поток не будет остановлен. Это быть может не разумеется для большинства клиентских программ, тем более это быть может особо полезно для всех серверов и неких случаев на клиенте. В этом также отличие от способа Execute класса TThread. Способ Execute вызывается лишь один раз. Когда завершается Execute, то поток также завершен.
Есть и остальные различия меж TIdThread и TThread, но они не так значительны, как Run и Execute.
16.12. Класс TIdThreadComponent
Класс TIdThreadComponent – это компонент, который дозволяет для вас зрительно строить новейшие потоки, просто добавляя событие в дизайн тайм. Это основано на как бы зрительной инкапсуляции TIdThread, что дозволяет делать, как мы выражаемся, новейшие потоки до боли просто.
Для использования TIdThreadComponent добавьте его на вашу форму, определите событие OnRun и установите свойство Active. Пример использования TIdThreadComponent можно узреть в демонстрационном примере TIdThreadComponent, легкодоступным с веб-сайта проекта.
16.13. Способ TIdSync
Способ TThread имеет способ Synchronize, но он не имеет способности передавать характеристики в, как люди привыкли выражаться, синхронизационные способы. Способ TIdSync имеет такую возможность. Способ TIdSync также дозволяет, вообщем то, возвращать значения из главенствующего потока.
16.14. Класс TIdNotify
Синхронизация великолепна, когда количество потоков маленькое. Тем более в, как заведено выражаться, серверных приложениях со почти всеми потоками, они стают узеньким горлышком и могут серьезно понизить производительность. Для решения данной нам задачи, должны употребляться оповещения. В Indy класс TIdNotify реализует оповещения. Оповещения разрешают разговаривать с основным потоком, но в отличие от синхронизации потока не заблокируют его, пока оповещение обрабатывается. Оповещения выполняют функцию, схожую синхронизации, но без понижения производительности.
Тем более, оповещения имеют и ряд ограничений. Одно из их, что значение не быть может возвращено из, как всем известно, главенствующего потока, так как оповещения не останавливают вызывающий поток.
16.15. Класс TIdThreadSafe
Класс TIdThreadSafe – это базисный класс, для реализации, как мы с вами постоянно говорим, потоко-безопасных классов. Класс TIdThreadSafe никогда не употребляется сам по для себя и разработан лишь как базисный класс.
Indy содержит уже, как многие выражаются, готовых наследников: TIdThreadSafeInteger, TIdThreadSafeCardinal, TIdThreadSafeString, TIdThreadSafeStringList, TIdThreadSafeList. Эти классы могут, в конце концов, употребляться для потоко-безопасных версий типов integer, string и т.д.. Они, стало быть, могут потом могут безопасно употребляться в любом количестве потоков, без необходимости хлопотать о этом. В дополнение они поддерживают явное блокирование для расширенных целей.
16.16. Общие задачи
Большей, как большинство из нас привыкло говорить, неувязкой при работе с потоками является параллельное выполнение. Так как потоки выполняются параллельно, то имеется неувязка доступа к общим данным. При использовании потоков в приложении, появляются последующие препядствия:
При выполнение клиентов в потоках, приносит последующие трудности с конкурированием: 1. обновление, как заведено выражаться, пользовательского интерфейса из потока. 2. общение с основным потоком из как бы вторичных потоков. 3. доступ к данным, как всем известно, главенствующего потока из, как многие думают, вторичных потоков. 4. возвращение результата из потока. 5. определение момента завершения потока.
16.17. Узенькие места
Почти все создатели, стало быть, делают многопоточные приложения, которые работают нормально, пока количество потоков малюсенькое, но приложение также деградирует, когда количество потоков возрастает. Это эффект, как большинство из нас привыкло говорить, узенького горлышка. Эффект, как всем известно, узенького горлышка, мягко говоря, проявляется когда какой то кусочек кода перекрывает остальные потоки и остальные потоки обязаны ждать его завершения. Не принципиально как резв остальной код, неувязка лишь в одном медленном кусочке. Код будет, вообщем то, работать так быстро, как быстро работает его самая медленная часть.
Почти все создатели заместо поиска узенького горлышка, растрачивают свое время на улучшение частей кода, который они считают недостаточно, как мы выражаемся, скорым, тогда как узенькое горлышко не, вообщем то, дозволяет достичь какого-нибудь эффекта.
Традиционно устранение как бы 1-го узенького горлышка дает ускорение, больше чем сотки остальных оптимизаций. Потому, сфокусируйтесь на поиске, как многие выражаются, узенького горлышка. И лишь после чего смотрите иной код для вероятной оптимизации.
Несколько узеньких мест будет рассмотрено ниже.
16.17.1. Реализация критических секций
Критические секции действенным и, как все говорят, обычным методом разрешают управлять доступом к ресурсам, чтоб лишь один поток имел доступ к ресурсу сразу.
Нередко одна критическая секция употребляется для защиты множества ресурсов. Допустим, что ресурсы A, B и C имеют одну, как большая часть из нас постоянно говорит, общую критическую секцию для их защиты, хотя каждый ресурс независим. Неувязка возникает, когда употребляется B, то A и C также заблокированы. Критические секции обыкновенные и выделенная, как все говорят, критическая секция обязана, стало быть, употребляться для каждого ресурса.
Критические секции время от времени, вообщем то, могут заблокировать очень, как большая часть из нас постоянно говорит, много кода. Количество кода, меж способами Enter и Leave в критической секции обязано быть мало, как заведено выражаться, вероятным и почти всегда обязано употребляться несколько критических секций, ежели это может быть.
16.17.2. Класс TMREWS
Класс TMREWS может отдать существенно повышение производительности перед критическими секциями, ежели в основном доступ идет лишь по чтению и лишь время от времени по записи. Тем более, класс TMREWS наиболее непростой, чем критическая секция и требуется больше кода для установки блокировки. Для маленьких кусков кода, даже ежели запись мала, традиционно критическая секция так сказать работает лучше, чем TMREWS.
16.17.3. Синхронизация (Synchronizations)
Синхронизация традиционно употребляется для обновления, как все знают, пользовательского интерфейса.
Неувязка также состоит в том, что синхронизация останавливает поток, пока она не будет закончена. Так как лишь основной поток как бы обслуживает синхронизацию, лишь он может выполняться и другие потоки выстраиваются в очередь. Это, вообщем то, делает все синхронизации самым, как люди привыкли выражаться, основным узеньким горлышком.
Используйте оповещение везде где лишь может быть.
16.17.4. Обновление пользовательского интерфейса
Многопоточные приложения нередко делают очень много обновлений пользовательского интерфейса. Утрата производительности быстро наступает из-за задержек в обновлении пользовательского интерфейса. Почти всегда многопоточные приложения выполняются медлительнее, чем однопоточная версия.
Особенное внимание обязано быть изготовлено при реализации серверов. Сервер есть сервер и как бы основная его задачка обслуживать клиентов. Пользовательский интерфейс вторичен в данном случае. Потому пользовательский интерфейс лучше также сделать в отдельном приложении, которое будет особым клиентом для сервера. Другое решение – это применять оповещения либо пакетные обновления. В обоих этих вариантах пользовательский интерфейс, как большая часть из нас постоянно говорит, мало также будет заторможен, но лучше иметь пользовательский интерфейс, который будет задержан на одну секунду, чем 200 клиентов, каждый их которых, мягко говоря, будет задержан на ту же секунду. Королями так сказать являются клиенты.