Почему новые 32-разрядные операционные системы называются многопотоковыми?
Многозадачность и многопотоковость - эти два термина часто упоминают, когда речь заходит о работе 32-разрядных версий операционных систем Windows и OS/2. Что под этим понимается? Многозадачная операционна система обеспечивает одновременное исполнение двух или более программ за счет выделения каждой из них процессорного времени. Многопотоковость же подразумевает использование под управлением ОС нескольких параллельных потоков вычислений, относящихс к одной прикладной программе.
Большинство из нас достаточно хорошо понимают, что такое многозадачность, но мало кто сможет правильно объяснить смысл многопотоковости. Например, что такое "несколько параллельно исполняемых потоков"? А что такое просто "исполняемый поток"?
В чем отличие многопотоковости от многозадачности, и есть между ними что-то общее? Или другой, более существенный вопрос - как влияет использование многопотоковости на прикладные программы? Действительно ли новые программы Windows, где реализована многопотоковость, работают лучше, чем однопотоковые ? Или многопотоковость - это всего лишь очередная игрушка для программистов.
Как видите, сложных вопросов здесь немало. К счастью, ответить на них нетрудно. Совсем не обязательно быть программистом для Windows, чтобы понять, что такое исполняемые потоки и как они используются. В этой статье мы дадим определение термину "поток" (thread) и рассмотрим, как соответствующие механизмы работают в Windows 95 и Windows NT.
Далее мы детально рассмотрим особенности применени потоков как в прикладных программах, так и в самой операционной системе и в завершение показажем пример двух версий одной прикладной программы - много- и однопотоковый варианты с целью убедиться, насколько сильно влияние от внедрения многопотоковости.
В Windows 3.1 каждая исполняемая прикладна программа представляет собой отдельную задачу (task). Операционная система делит между ними процессорное время, используя для этих целей механизм диспетчеризации, реализующий принцип кооперативной многозадачности. В соответствии с этим принципом каждая прикладная программа сама уступает управление операционной системе, которая затем передает его другой задаче. Диспетчеризация осуществляется на уровне задач, поэтому переключение с одной из них на другую означает просто следующую операцию: Windows сохраняет содержимое рабочих регистров задачи, активная обработка которой приостанавливается, и восстанавливает значени регистров для той задачи, которой передаетс управление. В результате обработка второй задачи возобновляется с того места, где она была приостановлена.
В среде Win32 все обстоит иначе. Программы больше не воспринимаются как простые задачи; теперь их рассматривают как процессы (process) и потоки (thread). Процесс - это отдельная исполняемая программа с памятью и другими выделенными ей ресурсами. Поток - это последовательность исполняемых команд. Процесс может состоять из единственного потока, а может содержать их несколько. На процессы и потоки распространяетс вытесняющая многозадачность. В результате отпадает необходимость встраивания в каждую прикладную программу механизма уступки управления, обеспечивающего выделение процессорного времени другим программам. Вместо этого, в операционной системе реализован сложный механизм диспетчеризации, обеспечивающий принудительное прерывание, или, другими словами, вытеснение, активного потока в тот момент, когда наступает очередь другого. Это приводит к более четкой обработке многозадачности: при одновременном исполнении нескольких программ процессорное время распределяется между ними более последовательно и улучшается отклик программ на ввод. Чувствительность реакции программы является ее важным показателем, поскольку первое, что подмечает пользователь при неверной работе одной из программ, - это вялая реакция на манипуляции с мышью или клавиатурой.
Для того чтобы разобраться, в чем разница, давайте представим себе следующее. Каждая задача в 16-разрядной среде Windows представляет собой единственный исполняемый поток. Его выполнение осуществляетс линейно - сверху вниз, - следуя по по пути , представленному потоком. Если же рассматривать 32-разрядную среду Windows, то первоначально любой процесс инициируется как отдельный поток. Но затем из него могут выделиться дополнительные потоки дл выполнения фоновых задач, например, форматировани диска или проведения вычислений в электронной таблице. По смыслу это напоминает развилку дорог: процессор может работать по любому маршруту, причем обработка по каждому из них протекает независимо от других (режим многозадачности), так, как будто все они - самостоятельные программы.
Распределяя процессорное время среди нескольких потоков, операционные системы Windows 95 и OS/2 создают иллюзию параллельно протекающих потоков. Если компьютер оснащен несколькими процессорами, то Windows NT действительно обрабатывает эти потоки одновременно, выделяя на обслуживание каждого из них свой особый процессор. Подобное явление именуется симметричной мультипроцессорной обработкой (symmetric multiprocessing), или просто SMP. В настоящее врем персональные компьютеры, содержащие два или более процессоров, пока редкость, но не далек тот день, когда любой из нас будет работать на машине, имеющей от 4 до 16 процессоров. Когда это произойдет, у каждого из нас возникнет потребность в SMP.
В Windows 95, также как в Windows NT и OS/2,не процесс и не задача, а именно поток является базовым элементом механизма выполнения программ. При необходимости одновременного выполнения нескольких операций в рамках одной прикладной программына реализацию каждой из них выделяется отдельный поток. Задача предоставления потоку необходимого процессорного времени возлагается исключительно на операционную систему. В отличие от этого 16-разрядная прикладна система может параллельно выполнять два действия, только если содержит специальный хитроумный механизм, позволяющий чередовать операции. Обычно это означает, что каждое действие разбивается на мелкие ступени, обрабатываемые попеременно. Если обработка на отдельной ступени длится дольше обычного, то друга часть программы автоматически теряет необходимые такты. Если это происходит с операциями приема и обработки команд пользователя, то у него внезапно возникает ощущение, что система стала чрезмерно медлительной и вялой.
На рис. 1 показана схема, иллюстрирующая пример двух вариантов некоторого гипотетического текстового процессора. Разница между вариантами состоит в том, что один из них отражает работу на базе одного, а другой - на базе нескольких потоков. В некоторый момент пользователь выбирает команду "Печать" из меню "Файл". Система начинает посылать на принтер текст документа. Печать организуется в фоновом режиме, так что пользователь имеет возможность продолжать работу.
Рис.1. Схема работы программы с одним и несколькими потоками
Один поток | Несколько потоков
________ | ________
| | | | | |
| Начало | | | Начало | |
|________| | |________| |
| | | |
___\|/__ | ___\|/__ |
| | | | | |
| Файл | | | | Файл | | |
| Печать | | | Печать |-------------
|________| | |________| | |
| ________ | | | |
---> | Печать | | ____\|/____ | __\|/______
_______ |________| | | Обработка | | | Печать |
| Ввод | | | | ввода | | | документа |
|_______|<--- | |___________| | |___________|
| ________ | | | |
---> | Печать | | | | |
_______ |________| | |<----------------
| Ввод | | | | |
|_______|<--- | __\|/__ |
| | | | |
__\|/__ | | Конец | |
| | | |_______| |
| Конец | | |
|_______| | Поток обработки | Поток выполнени
| ввода | печати
Сначала рассмотрим случай, когда используется только один поток - на рисунке это слева. После начала печати программа начинает попеременно то проверять очередь входных сообщений, то пересылать очередную порцию данных на принтер. Логика работы непроста, поскольку процесс печати должен быть разбит на отдельные ступени и за каждое обращение выполняется лишь одна ступень. Но это не самое страшное. Плохо то, что от этого страдает чувствительность системы, поскольку обработка сообщений от мыши и клавиатуры выполняется с тем же приоритетом, что и процесс печати. Если в тот момент, когда программа приступит к выводу очередного блока данных на печать, пользоаптель нажмет клавишу, сообщение об этом застрянет во входной очереди, причем до тех пор, пока не завершится текущее взаимодействие с принтером. Такие задержки ввода можно сократить, если процесс печати разбить на более мелкие ступени, требующие меньших затрат времени на обработку. Однако у подобного решения есть и недостатки: увеличение времени, затрачиваемого на обработку ввода, будет вызывать замедление процесса печати.
В другой программе, где используется несколько потоков, реализация фоновой печати не представляет особых трудностей; просто запускается второй поток, единственное назначение которого - вывод документа на принтер. При работе этого потока операционная система быстро переключается с потока обработки ввода на поток печати и обратно, что создает впечатление их одновременного исполнения. Более того, такая обработка происходит с максимальной эффективностью. Когда поток для обслуживания ввода находится в ожидании, львина доля процессорного времени выделяется потоку печати. Однако, как только пользователь нажимает кнопку на клавиатуре или начинает щелкать мышью, обработка потока печати приостанавливается, и управление передаетс потоку ввода. Ожиданий на обработку ввода не происходит, и как только возникают входные сообщения , программа реагирует немедленно.
Наверняка, вы обращали внимание, насколько замедляется работа в Windows 3.1, когда Диспетчер печати (Print Manager) выводит на печать объемный документ. Теперь причина ясна: нет возможности осуществить печать этого документа в отдельном потоке. Вместо этого Диспетчер печати поступает следующим образом: печатает несколько символов, уступать управление другим программам, затем печатать следующую порцию символов, вновь уступать управление и т. д., пока печать ни будет завершена. В то время как очередная порция данных передается принтеру, все набранные с клавиатуры символы и прочие сообщени выстраиваются в очередь на обработку. В отличие от этого выполнение фоновой печати в Windows 95, Windows NT и OS/2 происходит значительно легче - частично за счет того, что передача информации на принтер выполняется через специально выделенный для этих целей поток, который приостанавливается при вводе. Приведенный пример - явное тому подтверждение, насколько велика разница при использовании многопотоковой обработки.
При решении вопроса, какому потоку отдать предпочтение и сколько выделить ему времени, операционной системе приходится учитывать немало факторов. Недостаточно просто установить таймер, чтобы каждые несколько миллисекунд управление передавалось следующему очередному потоку. В этом случае выигрыш по сравнению с обычными 16-разрядными системами был бы не значительным.
Основным фактором, учитываемым при диспетчеризации, служит ПРИОРИТЕТ выполнения (execution priority) каждого потока. В Windows 95 и Windows NT каждому потоку присваивается свой приоритет - от 0 до 31; чем больше число, тем выше приоритет. Приоритет 31 резервируется под особенно критичные операции, например, например прием данных в реальном режиме времени. Приоритет 0 назначается операционной системой некоторым второстепенным задачам, выполнение которых происходит в то время, когда нет других задач. Большинство потоков работают с приоритетом от 7 до 11. Каждые несколько миллисекунд диспетчер операционной системы пересматривает все работающие в системе потоки и выделяет процессорное время потоку с наивысшим приоритетом. Если потоков с одинаково высоким приоритетом несколько, то управление передается тому, который простаивает дольше других. В общем, в первую очередь всегда обрабатываются потоки с более высоким приоритетом, а потоки с низким приоритетом никогда не вытесняют потоки с более высоким.
Не означает ли это, что если в системе одновременно присутствуют несколько потоков - один с приоритетом 10 и четыре с приоритетом 9,- то до выполнения потоков с приоритетом 9 дело никогда не дойдет? Но это не так. Вспомните, что все, что происходит в Windows, управляется с помощью сообщений. Прикладная программа Windows не может принимать сигналы непосредственно от мыши или с клавиатуры; она получает соответствующие сообщения от операционной системы, которая информирует данную программу о вводе информации от мыши или с клавиатуры. Когда очередь сообщений, направляемых потоку с приоритетом 10, исчерпана полностью, он переводится в состояние ожидания (до получения новых сообщений). Это позволяет перейти к обработке потоков с более низким приоритетом. При этом приостановленный поток не получает процессорное время до момента его активизации в ответ на какое-либо событие. Если приостановленному потоку с приоритетом 10 получает новое сообщение, он вытесняет исполняемый в текущий момент поток с приоритетом 9 и его обработка возобновляется. До этого момента он не получит процессорное время. В состоянии ожидания ввода новых сообщений большинство потоков проводит длительное время. В результате почти во всех случаях, кроме экстремальных, даже потоки с приоритетом 0 получают достаточно времени. Тем не менее бездарно написанна прикладная программа может замедлить выполнение остальных работающих программ. К счастью, плохо проработанная схема организации потоков проявляется уже на стадии тестирования; поэтому большинство разработчиков смогут устранить проблемы до того, как их продукт попадет к пользователям.
Система Windows 95 сама производит всевозможные манипуляции со значениями приоритетов программ, стремясь максимально улучшить их реакцию. Например, Windows 95 автоматически увеличивает на один приоритеты потоков активных прикладных программ, что дает им преимущество над исполнением фоновых программ. В связи с этим исполняемая приоритетная программа более оперативно реагирует на ввод информации пользователем. Кроме того, диспетчер временно повышает приоритет потока, получающего сообщение, на 1 или 2, что обеспечивает приоритетность обработки этого сообщени по сравнению с остальными операциями. Затем приоритет этого потока постепенно снижается до первоначального уровня, чтобы понапрасну не расходовать ресурсы процессора.
Как используются многочисленные потоки в современных прикладных программах? Не имея доступа к исходному тексту программы, непросто точно ответить на вопрос , как именно реализованы потоки в программах. Но нетрудно ответить на вопрос, сколько именно потоков задействовано в некоторой программе. В состав Windows 95 входит утилита System Monitor (Мониторинг системных ресурсов), загрузить которую можно из меню Start|Programs|Accessories|System Tools (Старт|Программы|Реквизиты|Системные средства). (Если данная утилита еще не установлена, то выберите из меню следующую последовательность команд Start|Settings|Control Panel (Старт|Настройка|Панель Управления) и раскройте пиктограмму Add/Remove Programs (Добавить/Удалить Программы)). Один из параметров, который System Monitor умеет отображать на экране, - это количество активных потоков в системе. Вызвав прикладную программу при работающей утилите System Monitor и наблюдая за количеством потоков, можно определить число потоков данной программы. Если же программа использует, например, специальный поток дл фоновой печати, то вы, вероятно, увидите, как при запуске некоторого документа на печать количество потоков увеличится на 1.
Оказывается, система Windows 95 сама использует некоторое количество потоков. Подтверждением этому служит тот факт, что, даже если не выполняется ни одна программа, кроме System Monitor, количество рабочих потоков в этот момент не бывает меньше 12-13. В Windows 95 специально выделен отдельный поток дл обработки ошибок; это облегчает восстановление операционной системы в случае, когда некотора прикладная программа вызывает ошибку общей защиты (General Protection Fault) либо приводит к другим серьезным нарушениям. Кроме того, используется особый поток для отработки анимационных (изменяющих свой внешний вид) курсоров; ему присваивается высокий приоритет, однако большую часть времени этот поток находится в состоянии ожидания, пока ни придет момент для смены "кадра".
Оболочка Windows 95 также использует преимущества заложенного в операционной системе механизма многопотоковой обработки. Например, каждое открываемое пользователем окно управляется в самостоятельном потоке. Если вы копируете некоторый файл переносом из одного окна в другое, то операция копирования будет выполняться потоком, управляющим принимающим окном. Инструментальная панель TaskBar (Панель задач), аналогично любому окну, отображаещему "рабочий стол", имеет собственный поток. Модули DLL, реализующие элементы надстройки оболочки, исполняются как самостоятельные потоки, поэтому если работа конкретной надстройки завершается, то на работу всей оболочки это не сказывается. И сама оболочка использует один поток, с помощью которого собираются и объединяются все сведения об изменениях в файловой системе. Таким образом, Некоторое окно не будет перегружено потоком сообщений, если кто-то решит в открытом окне DOS скопировать 100 файлов в папку, содержимое которой отражается в окне утилиты Explorer (Проводник).
В Windows NT технология потоков используется еще шире. Например, есть поток с приоритетом 0, который очищает содержимое неиспользуемых страниц памяти. Есть еще специальный поток для приема информации от мыши и клавиатуры, дальнейшего преобразования этой информации в сообщения и распределения их по различным очередям.
Одних теоретических рассуждений, конечно, не достаточно. В конце концов, для наглядности следует на практике увидеть разницу при использовании среды со многими потоками. Я подготовил для этих целей две программы Windows 95 - Threads1 и Threads2, - которые можно получить в информационной службе PC Magazine Online. Функционально обе программы выполняют одну и ту же задачу; но в Threads1 для выполнения этой работы используется один поток, тогда как в Threads2 - три. Разница между этими программами не столь значительна (в Threads1 реализовано множество искусственных приемов, чтобы создать иллюзию использования нескольких потоков), тем не менее, она достаточно наглядна.
Для начала загрузите программу Threads2 и выберите в меню Options (Варианты) команду Start Clock (Включить часы). При этом запускается второй поток, отвечающий за непрерывное вращение стрелок этих часов. Теперь уменьшите размер окна, уберите куда-нибудь в угол экрана и запустите любой прикладной пакет типа Excel или Word. Обратите внимание, что, когда Threads2 исполняется в фоновом режиме, скорость вращения стрелок часов сохраняется практически на прежнем уровне, как при работе в приоритетном плане. Кроме того, заметьте , что, когда производится ввод с клавиатуры в Word или Excel, происходит кратковременное замедление вращени стрелок. Это результат динамического увеличени приоритета, производимого операционной системой в ответ на появление входных сообщений. При нажатии любой клавиши поток ввода в Word или Excel временно получает более высокий приоритет. В результате поток, обслуживающий вращение стрелок, теряет несколько тактов работы процессора. Спустя 2-3 секунды поток обслуживания входной очереди получает прежний приоритет, и скорость вращения стрелок воостановливается.
Теперь, когда стрелки часов продолжают вращаться, выберите из меню Options команду Start Disk Check (Начать проверку диска). При этом запускается третий поток, в котором идет просмотр каждого каталога на диске C: и по завершении этой работы на экране отображается количество содержащихся на нем файлов и каталогов. Выполнение такой проверки обычно занимает несколько секунд, и во время нее скорость вращени стрелок замедляется приблизительно в два раза. Процессор не может два дела делать одновременно . Поэтому, когда в системе выполняются два потока с равным приоритетом и ни один из них не использует прямого взаимодействия с пользователем (имеется в виду, что ни один из потоков не просматривает очередь сообщений и не может быть переведен в состояние ожидания ввода от пользователя), в результате скорость выполнения каждого потока уменьшается вдвое по сравнению с обычной. Но даже если одновременно исполняются две фоновые операции, сравнительно емкие с точки зрения затрат процессорного времени, Threads2 продолжает нормально реагировать на действия извне. Раскрытие меню происходит быстро, а при вводе с клавиатуры в расположенное снизу поле редактировани заметных задержек не бывает.
Программе Threads1 предстоит выполнить аналогичный набор функций, но с использованием лишь одного потока. Обратите внимание, что при раскрытии меню стрелки часов останавливаются. Кроме того, остановка стрелок происходит, когда на экране появляется блок сообщений, стрелки остаются неподвижны также в течение всего процесса сканирования диска. Эту проблему частично можно устранить за счет хитроумных приемов программирования, однако, поверьте моему слову, в программе Threads1 уже использовано немало остроумных способов, чтобы вообще заставить стрелки вращаться, а сканирование диска выполнять без значительного ухудшения реакции на действия извне. Взаимодействие с полем ввода, расположенным в нижней части окна, протекает без заминок, хотя и наблюдаются некоторые рывки при наборе с клавиатуры, если выполняение этой операции происходит одновременно со сканированием диска.
Перечень возможных экспериментов, конечно, не ограничивается перечисленными вариантами. Можно, например, параллельно запустить вместе четыре экземпляра программы Threads2 и посмотреть, как ведут себя стрелки при переключении с одной программы на другую. Скорость вращения стрелок может служить грубым индикатором количества единиц процессорного времени, выделяемого каждому потоку, поскольку поток, обеспечивающий вращение стрелок, выполняет эту операцию с максимально возможной для него скоростью. Повторите аналогичный эксперимент с программой Threads1, и вы убедитесь, что при использовании только одного потока работа протекает значительно менее слажено; движение стрелок часов более неравномерно.