Жизнь процессов в вычислительной системе напоминает жизнь соседей в коммунальной квартире. Постоянное ожидание в очереди к местам общего пользования (к процессору?) и постоянная борьба за другие ресурсы (кто опять занял все конфорки на плите?). Для нормального функционирования процессов операционная система старается максимально обособить их друг от друга. Каждый процесс имеет свое собственное адресное пространство (каждая семья должна жить в отдельной комнате), нарушение которого, как правило, приводит к аварийной остановке процесса (вызов милиции). Каждому процессу, по возможности, предоставляются свои собственные дополнительные ресурсы (у каждой семьи желателен свой собственный холодильник). Тем не менее, для решения некоторых задач (приготовление праздничного стола на всю квартиру) процессы могут объединять свои усилия. Настоящая глава описывает причины, по которым взаимодействуют процессы, способы их взаимодействия и возникающие при этом проблемы (попытайтесь отремонтировать общую квартиру так, чтобы не переругались все проживающие в ней семьи).
Для достижения поставленной цели различные процессы (возможно, даже принадлежащие разным пользователям) могут исполняться псевдопараллельно на одной вычислительной системе или параллельно на разных вычислительных системах, взаимодействуя между собой.
Для чего процессам нужно заниматься совместной деятельностью? Какие существуют причины для их кооперации?
Одной из причин является повышение скорости работы. Когда один процесс ожидает наступления некоторого события (например, окончания операции ввода-вывода), другие в это время могут заниматься полезной работой, направленной на решение общей задачи. В многопроцессорных вычислительных системах программа разделяется на отдельные кусочки, каждый из которых будет исполняться на своем процессоре.
Второй причиной является совместное использование данных. Различные процессы могут, к примеру, работать с одной и той же динамической базой данных или с разделяемым файлом, совместно изменяя их содержимое.
Третьей причиной является модульная конструкция какой-либо системы. Типичным примером может служить микроядерный способ построения операционной системы, когда ее различные части представляют собой отдельные процессы, общающиеся путем передачи сообщений через микроядро.
Наконец, это может быть необходимо просто для удобства работы пользователя, желающего, например, редактировать и отлаживать программу одновременно. В этой ситуации процессы редактора и отладчика должны уметь взаимодействовать друг с другом.
Процессы не могут взаимодействовать не общаясь. Общение процессов обычно приводит к изменению их поведения в зависимости от полученной информации. Если деятельность процессов остается неизменной при любой принятой ими информации, то это означает, что они на самом деле не нуждаются во взаимном общении. Процессы, которые влияют на поведение друг друга путем обмена информацией, принято называть кооперативными или взаимодействующими процессами, в отличие от независимых процессов, не оказывающих друг на друга никакого воздействия и ничего не знающих о взаимном сосуществовании в вычислительной системе.
Различные процессы в вычислительной системе изначально представляют собой обособленные сущности. Работа одного процесса не должна приводить к нарушению работы другого процесса. Для этого, в частности, разделены их адресные пространства и системные ресурсы, и для обеспечения корректного взаимодействия процессов требуются специальные средства и действия операционной системы. Нельзя просто поместить значение, вычисленное в одном процессе, в область памяти, соответствующую переменной в другом процессе, не предприняв каких-либо дополнительных организационных усилий. Давайте рассмотрим основные аспекты организации совместной работы процессов.
Процессы могут взаимодействовать друг с другом только обмениваясь информацией. По объему передаваемой информации и степени возможного воздействия на поведение другого процесса все средства такого обмена можно разделить на три категории:
Сигнальные. Передается минимальное количество информации — один бит, “да” или “нет”. Используются, как правило, для извещения процесса о наступлении какого-либо события. Степень воздействия на поведение процесса, получившего информацию, минимальна. Все зависит от того, знает ли он, что означает полученный сигнал, надо ли на него реагировать и каким образом. Неправильная реакция на сигнал или его игнорирование могут привести к трагическим последствиям. Вспомним профессора Плейшнера из кинофильма “Семнадцать мгновений весны”. Сигнал тревоги — цветочный горшок на подоконнике — был ему передан, но проигнорирован. И к чему это привело?
Канальные. Общение процессов происходит через линии связи, предоставленные операционной системой, и напоминает общение людей по телефону, с помощью записок, писем или объявлений. Объем передаваемой информации в единицу времени ограничен пропускной способностью линий связи. С увеличением количества информации увеличивается и возможность влияния на поведение другого процесса.
Разделяемая память. Два или более процессов могут совместно использовать некоторую область адресного пространства. Созданием разделяемой памяти занимается операционная система (если, конечно, ее об этом попросят). Общение процессов напоминает совместное проживание студентов в одной комнате общежития. Возможность обмена информацией максимальна, как, впрочем, и влияние на поведение другого процесса, но требует повышенной осторожности (если вы переложили с места на место все вещи вашего соседа по комнате, а часть из них еще и выбросили, то представляете, как он отреагирует?). Использование разделяемой памяти для передачи/получения информации осуществляется с помощью средств обычных языков программирования, в то время как сигнальным и канальным средствам коммуникации для этого необходимы специальные системные вызовы. Разделяемая память представляет собой наиболее быстрый способ взаимодействия процессов в одной вычислительной системе.
При рассмотрении любого из средств коммуникации нас с вами будет интересовать не их физическая реализация (общая шина данных, прерывания, аппаратно разделяемая память и т. д. — мало ли чего придумает человечество), а логическая, определяющая, в конечном счете, механизм их использования. Некоторые важные аспекты логической реализации являются общими для всех категорий средств связи, некоторые относятся к отдельным категориям. Давайте кратко охарактеризуем основные вопросы, требующие освещения при изучении того или иного способа обмена информацией.
Могу ли я использовать средство связи непосредственно для обмена информацией сразу после создания процесса или первоначально необходимо предпринять некоторые действия по инициализации обмена? Так, например, для использования общей памяти различными процессами потребуется специальное обращение к операционной системе, которая выделит требуемую область адресного пространства. Но для передачи сигнала от одного процесса к другому никакая инициализация не нужна. В то же время, передача информации по линиям связи может потребовать первоначального резервирования такой линии для процессов, желающих обменяться информацией.
К этому же вопросу тесно примыкает вопрос о способе адресации при использовании средства связи. Если я передаю некоторую информацию, то я должен указать, куда я ее передаю. Если я желаю получить некоторую информацию, то мне нужно знать, откуда я могу ее получить.
Различают два способа адресации: прямую и непрямую. В случае прямой адресации взаимодействующие процессы непосредственно общаются друг с другом, при каждой операции обмена данными явно указывая имя или номер процесса, которому информация предназначена или от которого она должна быть получена. Если и процесс, от которого данные исходят, и процесс, принимающий данные, оба указывают имена своих партнеров по взаимодействию, то такая схема адресации называется симметричной прямой адресацией. Ни один другой процесс не может вмешаться в процедуру симметричного прямого общения двух процессов, перехватить посланные или подменить ожидаемые данные. Если только один из взаимодействующих процессов, например передающий, указывает имя своего партнера по кооперации, а второй процесс в качестве возможного партнера рассматривает любой процесс в системе, например, ожидает получения информации от произвольного источника, то такая схема адресации называется асимметричной прямой адресацией.
При непрямой адресации данные помещаются передающим процессом в некоторый промежуточный объект для хранения данных, имеющий свой адрес, из которого они могут быть затем изъяты каким-либо другим процессом. Примером такого объекта в повседневной жизни может служить обычная доска объявлений или рекламная газета. При этом передающий процесс не знает, как именно идентифицируется процесс, который получит информацию, а принимающий процесс не имеет представления об идентификаторе процесса, от которого он должен ее получить.
Естественно, что при использовании прямой адресации связь между процессами в классической операционной системе устанавливается автоматически, без дополнительных инициализирующих действий. Единственное, что нужно для использования средства связи, — это знать, как идентифицируются процессы, участвующие в обмене данными.
При использовании непрямой адресации инициализация средства связи может как требоваться, так и не требоваться. Информация, которой должен обладать процесс для взаимодействия с другими процессами, — это некий идентификатор промежуточного объекта для хранения данных, если он, конечно, не является единственным и неповторимым в вычислительной системе для всех процессов.
Следующий важный вопрос — это вопрос об информационной валентности связи. Слово валентность здесь использовано по аналогии с химией. Сколько процессов может быть одновременно ассоциировано с конкретным средством связи? Сколько таких средств связи может быть задействовано между двумя процессами?
Понятно, что при прямой адресации только одно данное средство связи может быть задействовано для обмена данными между двумя процессами, и только эти два процесса могут быть ассоциированы с ним. При непрямой адресации может существовать более двух процессов, использующих один и тот же объект для данных, и более одного объекта может быть использовано двумя процессами.
К этой же группе вопросов следует отнести и вопрос о направленности связи. Является ли связь однонаправленной или двунаправленной? Под однонаправленной связью мы будем понимать связь, при которой каждый процесс, ассоциированный с ней, может использовать средство связи либо только для приема информации, либо только для ее передачи. При двунаправленной связи, каждый процесс, участвующий в общении, может использовать связь и для приема, и для передачи данных. В коммуникационных системах принято называть однонаправленную связь симплексной, двунаправленную связь с поочередной передачей информации в разных направлениях — полудуплексной, а двунаправленную связь с возможностью одновременной передачи информации в разных направлениях — дуплексной. Прямая и непрямая адресация не имеют непосредственного отношения к направленности связи.
Как мы говорили выше, передача информации между процессами посредством линий связи является достаточно безопасной по сравнению с использованием разделяемой памяти и достаточно информативной по сравнению с сигнальными средствами коммуникации. Кроме того, разделяемая память не может быть использована для связи процессов, функционирующих на различных вычислительных системах. Возможно, именно поэтому каналы связи получили наибольшее распространение среди других средств коммуникации процессов. Коснемся некоторых вопросов, связанных с логической реализацией канальных средств коммуникации.
Может ли линия связи сохранять информацию, переданную одним процессом, до ее получения другим процессом или помещения в промежуточный объект? Каков объем этой информации? Иными словами, речь идет о том, обладает ли канал связи буфером и каков объем этого буфера. Здесь можно выделить три принципиальных варианта:
Буфер нулевой емкости или отсутствует. Никакая информация не может сохраняться на линии связи. В этом случае процесс, посылающий информацию, должен ожидать, пока процесс, принимающий информацию, не соблаговолит ее получить, прежде чем заниматься своими дальнейшими делами.
Буфер ограниченной емкости. Размер буфера равен n, то есть линия связи не может хранить до момента получения более чем n единиц информации. Если в момент передачи данных в буфере хватает места, то передающий процесс не должен ничего ожидать. Информация просто копируется в буфер. Если же в момент передачи данных буфер заполнен или места не достаточно, то необходимо задержать работу процесса отправителя до появления в буфере свободного пространства.
Буфер неограниченной емкости. Теоретически это возможно, но практически вряд ли реализуемо. Процесс, посылающий информацию, никогда не ждет окончания ее передачи и приема другим процессом.
При использовании канального средства связи с непрямой адресацией под емкостью буфера обычно понимается количество информации, которое может быть помещено в промежуточный объект для хранения данных.
Существует две модели передачи данных по каналам связи — поток ввода-вывода и сообщения. При передаче данных с помощью потоковой модели, операции передачи/приема информации вообще не интересуются содержимым данных. Процесс, прочитавший 100 байт из линии связи, не знает и не может знать, были ли они переданы одновременно, т. е. одним куском, или порциями по 20 байт, пришли они от одного процесса или от разных процессов. Данные представляют собой простой поток байт, без какой-либо их интерпретации со стороны системы. Примерами потоковых каналов связи могут служить pipe и FIFO, описанные ниже.
Одним из наиболее простых способов передачи информации между процессами по линиям связи является передача данных через pipe (канал, трубу или, как его еще называют в литературе, конвейер). Представим себе, что у вас есть некоторая труба в вычислительной системе, в один из концов которой процессы могут сливать информацию, а из другого конца принимать полученный поток. Естественно, что такой способ реализует потоковую модель ввода/вывода. Информацией о расположении трубы в операционной системе обладает только процесс, создавший ее. Этой информацией он может поделиться исключительно со своими наследниками — процессами-детьми и их потомками. Поэтому использовать pipe для связи между собой могут только родственные процессы, имеющие общего предка, создавшего этот канал связи.
Если разрешить процессу, создавшему трубу, сообщать об ее точном расположении в системе другим процессам, сделав вход и выход трубы каким-либо образом видимыми для всех остальных, например, зарегистрировав ее в операционной системе под определенным именем, мы получим объект, который принято называть FIFO или именованный pipe. Именованный pipe может использоваться для связи между любыми процессами в системе.
В модели сообщений процессы налагают на передаваемые данные некоторую структуру. Весь поток информации они разделяют на отдельные сообщения, вводя между данными, по крайней мере, границы сообщений. Примером границ сообщений являются точки между предложениями в сплошном тексте или границы абзаца. Кроме того, к передаваемой информации может быть присоединены указания на то, кем конкретное сообщение было послано и для кого оно предназначено. Примером указания отправителя могут служить подписи под эпиграфами в книге. Все сообщения могут иметь одинаковый фиксированный размер или могут быть переменной длины. В вычислительных системах используются разнообразные средства связи для передачи сообщений: очереди сообщений, sockets (гнезда) и т.д. Часть из них мы рассмотрим подробнее в дальнейшем, в частности очереди сообщений будут рассмотрены в главе 6, а гнезда (иногда их еще называют по транслитерации английского названия без перевода – сокеты) в главе 14.
И потоковые линии связи, и каналы сообщений могут иметь или не иметь буфер. Когда мы будем говорить о емкости буфера для потоков данных, мы будем измерять ее в байтах. Когда мы будем говорить о емкости буфера для сообщений, мы будем измерять ее в сообщениях.
4.3.4. Надежность средств связи
Одним из существенных вопросов при рассмотрении всех категорий средств связи является вопрос об их надежности. Из житейского опыта мы знаем, как тяжело расслышать собеседника по вечно трещащему телефону. Некоторые полученные телеграммы вызывают чувство глубокого недоумения: “Прибду пыездом в вонедельник 33 июня в 25.34. Пама”, а некоторые вообще не доставляются.
Мы будем называть способ коммуникации надежным, если при обмене данными выполняются следующие четыре условия:
Не происходит потери информации.
Не происходит повреждения информации.
Не появляется лишней информации.
Не нарушается порядок данных в процессе обмена.
Очевидно, что передача данных через разделяемую память является надежным способом связи. То, что мы сохранили в разделяемой памяти, будет считано другими процессами в первозданном виде, если, конечно, не произойдет сбоя в питании компьютера. Для других средств коммуникации, как видно из приведенных выше примеров, это не всегда верно.
Каким образом в вычислительных системах пытаются бороться с ненадежностью коммуникаций? Давайте рассмотрим возможные варианты на примере обмена данными через линию связи с помощью сообщений. Для обнаружения повреждения информации будем снабжать каждое передаваемое сообщение некоторой контрольной суммой, вычисленной по посланной информации. При приеме сообщения контрольную сумму будем вычислять заново и проверять ее соответствие пришедшему значению. Если данные не повреждены (контрольные суммы совпадают), то подтвердим правильность их получения. Если данные повреждены (контрольные суммы не совпадают), то сделаем вид, что сообщение к нам не поступило. Вместо контрольной суммы можно использовать специальное кодирование передавамых данных с помощью кодов, исправляющих ошибки. Такое кодирование позволяет при числе искажений информации, не превышающем некоторого значения, восстановить начальные неискаженные данные. Если по прошествии некоторого интервала времени подтверждение о правильности полученной информации не придет на передающий конец линии связи, то информацию будем считать утерянной, и пошлем ее повторно. Для того чтобы избежать двойного получения одной и той же информации, на приемном конце линии связи должен производиться соответствующий контроль. Для гарантии правильного порядка получения сообщений будем их нумеровать. При приеме сообщения с номером, не соответствующим ожидаемому, поступаем с ним как с утерянным и ждем сообщения с правильным номером
Подобные действия могут быть возложены:
на операционную систему;
на процессы, обменивающиеся данными;
совместно на систему и процессы, разделяя их ответственность. Операционная система может обнаруживать ошибки при передаче данных и извещать об этом взаимодействующие процессы для принятия ими решения о дальнейшем поведении.
Наконец, важным вопросом при изучении средств обмена данными является вопрос прекращения обмена. Здесь нужно выделить два аспекта: требуются ли от процесса какие-либо специальные действия по прекращению использования средства коммуникации, и влияет ли такое прекращение на поведение других процессов. Для способов связи, которые не подразумевали никаких инициализирующих действий, обычно ничего специального для окончания взаимодействия предпринимать не надо. Если же установление связи требовало некоторой инициализации, то, как правило, при ее завершении необходимо выполнение ряда операций, например, сообщения операционной системе об освобождении выделенного связного ресурса.
Если кооперативные процессы прекращают взаимодействие согласовано, то такое прекращение не влияет на их дальнейшее поведение. Иная картина наблюдается при несогласованном окончании связи одним из процессов. Если какой-либо из взаимодействующих процессов, не завершивших общение, находится в этот момент в состоянии ожидания получения данных, либо попадает в такое состояние позже, то операционная система обязана предпринять некоторые действия для того, чтобы исключить вечное блокирование этого процесса. Обычно это либо прекращение работы ожидающего процесса, либо его извещение о том, что связи больше нет (например, с помощью передачи заранее определенного сигнала).
Рассмотренные выше аспекты логической реализации относятся к средствам связи, ориентированным на организацию взаимодействия различных процессов. Однако усилия, направленные на ускорение решения задач в рамках классических операционных систем, привели к появлению совершенно иных механизмов, к изменению самого понятия “процесс”.
В свое время внедрение идеи мультипрограммирования позволило повысить пропускную способность компьютерных систем, т.е. уменьшить среднее время ожидания результатов работы процессов. Но любой отдельно взятый процесс в мультипрограммной системе никогда не может быть выполнен быстрее, чем при выполнении в однопрограммном режиме на том же вычислительном комплексе. Тем не менее, если алгоритм решения задачи обладает определенным внутренним параллелизмом, мы могли бы ускорить его работу, организовав взаимодействие нескольких процессов. Рассмотрим следующий пример. Пусть у нас есть следующая программа на псевдоязыке программирования:
Ввести массив a |
Ввести массив b |
Ввести массив c |
a = a + b |
c = a + c |
Вывести массив c |
При выполнении такой программы в рамках одного процесса этот процесс четырежды будет блокироваться, ожидая окончания операций ввода-вывода. Но наш алгоритм обладает внутренним параллелизмом. Вычисление суммы массивов a + b можно было бы делать параллельно с ожиданием окончания операции ввода массива c.
Ввести массив a |
|
Ожидание окончания операции ввода |
|
Ввести массив b |
|
Ожидание окончания операции ввода |
|
Ввести массив с |
|
Ожидание окончания операции ввода |
a = a + b |
c = a + c |
|
Вывести массив с |
|
Ожидание окончания операции вывода |
|
Такое совмещение операций по времени можно было бы реализовать, используя два взаимодействующих процесса. Для простоты будем полагать, что средством коммуникации между ними служит разделяемая память. Тогда наши процессы могут выглядеть следующим образом:
Процесс 1 |
|
Процесс 2 |
Ввести массив a |
|
Ожидание ввода |
Ожидание окончания операции ввода |
|
массивов a и b |
Ввести массив b |
|
|
Ожидание окончания операции ввода |
|
|
Ввести массив с |
|
|
Ожидание окончания операции ввода |
|
a = a + b |
c = a + c |
|
|
Вывести массив с |
|
|
Ожидание окончания операции вывода |
|
|
Казалось бы, мы предложили конкретный способ ускорения решения задачи. Однако в действительности дело обстоит не так просто. Второй процесс должен быть создан, оба процесса должны сказать операционной системе, что им необходима память, которую они могли бы разделить с другим процессом, и, наконец, нельзя забывать о переключении контекста. Поэтому реальное поведение процессов будет выглядеть примерно так.
Процесс 1 |
|
Процесс 2 |
Создать процесс 2 |
|
|
Переключение контекста | ||
|
|
Выделение общей памяти |
|
|
Ожидание ввода a и b |
Переключение контекста | ||
Выделение общей памяти |
|
|
Ввести массив a |
|
|
Ожидание окончания операции ввода |
|
|
Ввести массив b |
|
|
Ожидание окончания операции ввода |
|
|
Ввести массив с |
|
|
Ожидание окончания операции ввода |
|
|
Переключение контекста | ||
|
|
a = a + b |
Переключение контекста | ||
c = a + c |
|
|
Вывести массив с |
|
|
Ожидание окончания операции вывода |
|
|
Как видим, мы можем не только не выиграть во времени решения задачи, но даже и проиграть, так как временные потери на создание процесса, выделение общей памяти и переключение контекста могут превысить выигрыш, полученный за счет совмещения операций.
Для того, чтобы реализовать нашу идею, введем новую абстракцию внутри понятия “процесс” – нить исполнения или просто нить (в англоязычной литературе используется термин thread). Нити процесса разделяют его программный код, глобальные переменные и системные ресурсы, но каждая нить имеет свой собственный программный счетчик, свое содержимое регистров и свой собственный стек. Теперь процесс представляется как совокупность взаимодействующих нитей и выделенных ему ресурсов. Процесс, содержащий всего одну нить исполнения, идентичен процессу в том смысле, который мы употребляли ранее. Для таких процессов мы в дальнейшем будем использовать термин “традиционный процесс”. Иногда нити называют облегченными процессами или мини-процессами, так как во многих отношениях они подобны традиционным процессам. Нити, как и процессы, могут порождать нити-потомки, правда, только внутри своего процесса, и переходить из состояния в состояние. Состояния нитей аналогичны состояниям традиционных процессов. Из состояния рождение процесс приходит содержащим всего одну нить исполнения. Другие нити процесса будут являться потомками этой нити прародительницы. Мы можем считать, что процесс находится в состоянии готовность, если хотя бы одна из его нитей находится в состоянии готовность и ни одна из нитей не находится в состоянии исполнение. Мы можем считать, что процесс находится в состоянии исполнение, если одна из его нитей находится в состоянии исполнение. Процесс будет находиться в состоянии ожидание, если все его нити находятся в состоянии ожидание. Наконец, процесс находится в состоянии завершил исполнение, если все его нити находятся в состоянии завершили исполнение. Пока одна нить процесса заблокирована, другая нить того же процесса может выполняться. Нити разделяют процессор так же, как это делали традиционные процессы, в соответствии с рассмотренными алгоритмами планирования.
Поскольку нити одного процесса разделяют существенно больше ресурсов, чем различные процессы, то операции создания новой нити и переключения контекста между нитями одного процесса занимают существенно меньше времени, чем аналогичные операции для процессов в целом. Предложенная нами схема совмещения работы в терминах нитей одного процесса получает право на существование.
Нить 1 |
|
Нить 2 |
Создать нить 2 |
|
|
Переключение контекста нитей | ||
|
|
Ожидание ввода a и b |
Переключение контекста нитей | ||
Ввести массив a |
|
|
Ожидание окончания операции ввода |
|
|
Ввести массив b |
|
|
Ожидание окончания операции ввода |
|
|
Ввести массив с |
|
|
Ожидание окончания операции ввода |
|
|
Переключение контекста нитей | ||
|
|
a = a + b |
Переключение контекста нитей | ||
c = a + c |
|
|
Вывести массив с |
|
|
Ожидание окончания операции вывода |
|
|
Различают операционные системы, поддерживающие нити на уровне ядра и на уровне библиотек. Все выше сказанное справедливо для операционных систем, поддерживающих нити на уровне ядра. В них планирование использования процессора происходит в терминах нитей, а управление памятью и другими системными ресурсами остается в терминах процессов. В операционных системах, поддерживающих нити на уровне библиотек пользователей, и планирование процессора, и управление системными ресурсами осуществляется в терминах процессов. Распределение использования процессора по нитям в рамках выделенного процессу временного интервала осуществляется средствами библиотеки. В таких системах блокирование одной нити приводит к блокированию всего процесса, ибо ядро операционной системы ничего не знает о существовании нитей. По сути дела, в таких вычислительных системах просто имитируется наличие нитей исполнения.
В дальнейшем тексте этой части книги для простоты изложения мы будем использовать термин “процесс”, хотя все сказанное будет относиться и к нитям исполнения.
Для достижения поставленной цели различные процессы могут исполняться псевдопараллельно на одной вычислительной системе или параллельно на разных вычислительных системах, взаимодействуя между собой. Причинами для совместной деятельности процессов обычно являются: необходимость ускорения решения задачи, совместное использование обновляемых данных, удобство работы или модульный принцип построения программных комплексов. Процессы, которые влияют на поведение друг друга путем обмена информацией, называют кооперативными или взаимодействующими процессами, в отличие от независимых процессов, не оказывающих друг на друга никакого воздействия и ничего не знающих о взаимном сосуществовании в вычислительной системе.
Для обеспечения корректного обмена информацией операционная система должна предоставить процессам специальные средства связи. По объему передаваемой информации и степени возможного воздействия на поведение процесса, получившего информацию, их можно разделить на три категории: сигнальные, канальные и разделяемую память. Через канальные средства коммуникации информация может передаваться в виде потока данных или в виде сообщений и накапливаться в буфере определенного размера. Для инициализации общения процессов и его прекращения могут потребоваться специальные действия со стороны операционной системы. Процессы, связываясь друг с другом, могут использовать непрямую, прямую симметричную и прямую асимметричную схемы адресации. Существуют одно- и двунаправленные средства передачи информации. Средства коммуникации обеспечивают надежную связь, если при общении процессов не происходит потери информации, не происходит повреждения информации, не появляется лишней информации, не нарушается порядок данных.
Усилия, направленные на ускорение решения задач в рамках классических операционных систем, привели к появлению новой абстракции внутри понятия “процесс” - нити исполнения или просто нити. Нити процесса разделяют его программный код, глобальные переменные и системные ресурсы, но каждая нить имеет свой собственный программный счетчик, свое содержимое регистров и свой собственный стек. Теперь процесс представляется как совокупность взаимодействующих нитей и выделенных ему ресурсов. Нити могут порождать новые нити внутри своего процесса, они имеют состояния, аналогичные состояниям процесса, и могут переводиться операционной системой из одного состояния в другое. В системах, поддерживающих нити на уровне ядра, планирование использования процессора осуществляется в терминах нитей исполнения, а управление остальными системными ресурсами в терминах процессов. Накладные расходы на создание новой нити и на переключение контекста между нитями одного процесса существенно меньше, чем на те же самые действия для процессов, что позволяет на однопроцессорной вычислительной системе ускорять решение задач с помощью организации работы нескольких взаимодействующих нитей.
До сих пор все наши программы были однопоточными (single-threaded), т. е. у кода был лишь один поток исполнения. Казалось бы, при появлении сообщения каким-то чудом вызывается ваш обработчик, но в Windows все происходит совсем не так. Глубоко в недрах MFC-кода, компонуемого с вашей программой, спрятаны такие инструкции:
MSG message;
while ( GetMessage (&message, NULL, 0, 0))
{
::TranslateMessage(&inessage);
::DispatchMessage(&iressage);
}
Windows определяет, какие сообщения принадлежат программе, а функция GetMessage возвращает управление, как только появляется сообщение для обработки. Если сообщений нет, программа приостанавливается, и выполняются другие приложения. Когда сообщение в конце концов поступает, программа “пробуждается” Функция TranslateMessage транслирует сообщения WM_KEYDOWN в сообщения WM_CHAR, содержащие ASCII-символы, а функция DispatcbMessage передает управление (через оконный класс) коду выборки сообщений MFC, который вызывает Вашу функцию на основе таблицы обработчиков сообщений. Когда обработчик завершается, он возвращает управление MFC-коду, что в итоге вызывает возврат из DispatcbMessage.
Передача управления
Что произойдет, если одна из функций-обработчиков окажется “жадной” и израсходует 10 секунд процессорного времени? Во времена 16-разрядной системы компьютер просто завис бы на это время. Разве что можно было бы перемещать курсор мыши, да исполнялись бы какие-то задачи, управляемые прерываниями. В Win32 многозадачность организована куда лучше. Благодаря вытесняющей многозадачности другие приложения не зависнут — Windows, когда сочтет это нужным, просто приостановит выполнение “жадной” функции. Однако даже в Win32 ваша программа на эти 10 секунд была бы заблокирована. Она не смогла бы обрабатывать какие-либо сообщения, так как DispatcbMessage не возвращает управление до тех пор, пока его не возвратит злополучный обработчик.
Однако есть способ обойти эту проблему. Он срабатывает как в Winl6, так и в Win32. Надо просто заставить “жадную” функцию вести себя дружелюбнее, т. е. периодически отдавать управление — а для этого вставить в ее основной цикл такие операторы:
MSG message;
if (::PeekMessage(&message, NULL, 0, 0, PM_REMOVE))
{
::TranslateMessage(&message);
::DispatchMessage(&fflessage);
}
Функция PeekMessage работает так же, как и GetMessage — за исключением того, что возвращает управление немедленно, даже если для вашей программы нет никаких сообщений. В этом случае “жадная” функция продолжает поглощать процессорное время. Однако, как только появляется сообщение, вызывается обработчик, и по завершении его работы выполнение функции возобновляется.
Таймеры
Таймер Windows — это полезный элемент, иногда устраняющий необходимость в многопоточном программировании. Таймер можно использовать и для управления анимацией, .поскольку он не зависит от тактовой частоты процессора.
Для работы с таймерами необходимо вызывать функцию CWnd::SetTimer с параметром — интервал времени, после чего с помощью ClassWizard нужно определить обработчик сообщения WM_TIMER. После запуска таймера с заданным интервалом в миллисекундах сообщения WM_TIMER постоянно посылаются окну приложения до тех пор, пока не будет вызвана CWnd::KillTiiner или уничтожено окно. При необходимости можно задействовать несколько таймеров, каждый из которых идентифицируется целым числом. Поскольку Windows не является операционной системой реального времени, интервал значительно менее 100 миллисекунд приводит к потере точности.
Подобно другим сообщениям Windows, сообщения таймера могут заблокировать другие функции-обработчики в программе. К счастью, сообщения таймера не аккумулируются. Windows не ставит сообщения таймера в очередь, если в ней уже есть одно сообщение от данного таймера.
Обработка в периоды простоя
До появления многопоточности разработчики программ для Windows использовали периоды простоя (idle) для выполнения фоновых задач, например, разбивки документа на страницы. Теперь обработка во время простоя потеряла былое значение, но ей по-прежнему находится применение. Каркас приложений вызывает виртуальную функцию-член Onldle класса CWinApp, и Вы можете переопределить ее для выполнения фоновых вычислений Onldie вызывается из цикла обработки сообщений MFC-библиотеки, который на самом деле сложнее, чем приведенная выше простая последовательность GetMessage/TramlateMessage/Dispatch-Message. Обычно функция Onldle вызывается после того, как очередь сообщений у приложения опустеет, причем только один раз. Если Вы переопределяете ее, вызывается ваш код, но при отсутствии непрерывного потока сообщений это не происходит постоянно. Onldle в базовом классе обновляет кнопки панели инструментов и индикаторы состояния, а также “чистит” указатели на временные объекты. Имеет смысл переопределять эту функцию, если Вы хотите обновлять состояние элементов пользовательского интерфейса Ну, а то, что она не вызывается в отсутствие сообщений, значения не имеет, ведь пользовательский интерфейс и не должен в этом случае изменяться.
Переопределяя функцию CWinApp::Onldle, не забудьте вызвать Onldle базового класса. Иначе не произойдет ни обновления кнопок на панели инструментов, ни удаления временных объектов.
Onldle вообще не вызывается, если пользователь работает в модальном диалоговом окне или выбирает что-то в меню. При необходимости фоновой обработки модальных диалоговых окон или меню придется написать обработчик сообщения WM_ENTERIDLE, но его надо добавить в класс окна-рамки, а не в класс “вид”. Причина в том, что владельцем диалоговых окон всегда является основное окно-рамка приложения, а не окно представления.
1. Электромагнитная волна (в религиозной терминологии релятивизма - "свет") имеет строго постоянную скорость 300 тыс.км/с, абсурдно не отсчитываемую ни от чего. Реально ЭМ-волны имеют разную скорость в веществе (например, ~200 тыс км/с в стекле и ~3 млн. км/с в поверхностных слоях металлов, разную скорость в эфире (см. статью "Температура эфира и красные смещения"), разную скорость для разных частот (см. статью "О скорости ЭМ-волн")
2. В релятивизме "свет" есть мифическое явление само по себе, а не физическая волна, являющаяся волнением определенной физической среды. Релятивистский "свет" - это волнение ничего в ничем. У него нет среды-носителя колебаний.
3. В релятивизме возможны манипуляции со временем (замедление), поэтому там нарушаются основополагающие для любой науки принцип причинности и принцип строгой логичности. В релятивизме при скорости света время останавливается (поэтому в нем абсурдно говорить о частоте фотона). В релятивизме возможны такие насилия над разумом, как утверждение о взаимном превышении возраста близнецов, движущихся с субсветовой скоростью, и прочие издевательства над логикой, присущие любой религии.
4. В гравитационном релятивизме (ОТО) вопреки наблюдаемым фактам утверждается об угловом отклонении ЭМ-волн в пустом пространстве под действием гравитации. Однако астрономам известно, что свет от затменных двойных звезд не подвержен такому отклонению, а те "подтверждающие теорию Эйнштейна факты", которые якобы наблюдались А. Эддингтоном в 1919 году в отношении Солнца, являются фальсификацией. Подробнее читайте в FAQ по эфирной физике.