ЖУРНАЛ СТА 2/1997

клавиатура, дисплей, дисковый нако- питель, принтер и т. п. Представим, например, что несколько задач пыта- ются одновременно выводить данные на принтер. На распечатке в результа- те ничего, кроме странной мешанины символов, мы не увидим. В качестве другого примера рассмотрим ситуа- цию, когда в бортовом компьютере мирно летящего самолета МИГ29 сре- ди прочих работают две задачи. Одна из них, взаимодействуя с радиолока- ционной системой, выдает удаление и направление до цели, а другая задача использует эти данные для пуска ракет класса «воздухвоздух». Не исключено, что первая задача, записав в глобаль- ную структуру данных удаление до це- ли, будет прервана второй задачей, не успев записать туда направление до це- ли. В результате вторая задача считает из этой структуры ошибочные данные, что может привести к неудачному пус- ку со всеми вытекающими отсюда не- приятными последствиями. Прервись первая задача чуть позже, и все было бы нормально. Упомянутые здесь пробле- мы обусловлены времязависимыми ошибками (time dependent error), или «гонками» и характерны для многоза- дачных ОС, применяющих алгорит- мы планирования с вытеснением (кстати, системы с разделением вре- мени также относятся к категории «вытесняющих»). Приведенный пример показывает, что ошибки, обусловленные «гонками», а) характерны для работы с любыми ресурсами, доступ к которым имеют несколько задач, и б) происходят толь- ко в результате совпадения опреде- ленных условий, а потому с трудом обнаруживаются на этапе отладки. Вот возможные пути решения проб- лемы. 1. Не использовать алгоритмы плани- рования задач с вытеснением. Это ре- шение, правда, не всегда приемлемо. 2. Использовать специальный сервер ресурса, то есть задачу, ответствен- ную за упорядочивание доступа к ре- сурсу. В этом случае запрос на изме- нение значения глобальных данных посылается этому серверу в виде со- общения. Аналогичный подход при- меним и для физических устройств. Так, например, задача может послать данные на печать в виде сообщения, направленного к серверу принтера. 3. Запретить прерывания на время до- ступа к разделяемым данным. Кар- динальное решение, которое, впро- чем, не приветствуется в системах реального времени. 4. Использовать для упорядочивания доступа к глобальным данным сема- форы. Наиболее часто применяемое решение, которое, впрочем, может привести в некоторых случаях к «инверсии приоритетов». Последний пункт стоит прокоммен- тировать подробнее, поскольку поня- тие «семафор» встречается первый раз. Семафор — это как раз то средство, которое часто используется для син- хронизации доступа к ресурсам. В про- стейшем случае семафор представляет собой байтовую переменную, прини- мающую значение 0 или 1. Задача, пе- ред тем как использовать ресурс, захва- тывает семафор, после чего остальные задачи, желающие использовать тот же ресурс, должны ждать, пока семафор (ресурс) освободится. Существуют так- же так называемые счетные семафоры, где семафор представляет собой счет- чик. Пусть к системе подключено три принтера. Семафор, отвечающий за до- ступ к функциям печати, инициализи- руется со значением 3, а затем каждый раз, когда какаялибо задача запраши- вает семафор для осуществления пе- чати, его значение уменьшается на 1. После завершения печати задача осво- бождает семафор, в результате чего значение последнего увеличивается на 1. Если текущее значение семафора равно 0, то ресурс считается недоступ- ным, и задачи, запрашивающие печать, должны ждать, пока не освободится хотя бы один принтер. Таким образом может производиться синхронизация доступа множества задач к группе из 3 принтеров. Так как по своей сути се- мафор также представляет собой гло- бальную переменную, все неприят- ности, которые упоминались ранее в связи с самолетом МИГ29, по идее, должны поджидать нас и здесь. Одна- ко, так как работа с семафорами про- исходит на уровне системных вызо- вов, программист может быть уверен, что разработчики операционной сис- темы обо всем заранее позаботились. Проникнувшись сознанием того, на- сколько опасно изменять глобальные переменные в условиях, когда все во- круг так и норовят друг друга вытес- нить, читатель, наверно, не удивится, что участки кода программ, где проис- ходит обращение к разделяемым ре- сурсам, называются критическими секциями . Так как процессы обычно не имеют доступа к данным друг друга, а ресур- сы физических устройств, как прави- ло, управляются специальными зада- чамисерверами (драйверами), наибо- лее типична ситуация, когда «гонки» за доступ к глобальным переменным устраивают различные потоки, испол- няемые в рамках одного программно- го модуля. Для того чтобы гаранти- ровать, что критическая секция кода исполняется в каждый момент време- ни только одним потоком, используют механизм взаимоисключающего до- ступа, или попросту мутексов (Mutual Exclusion Locks, Mutex). Практически мутекс представляет собой разновид- ность семафора, который сигнализи- рует другим потокам, что критическая секция кода кемто уже выполняется. Критическая секция, использующая мутекс, должна иметь определенные суффиксную и префиксную части. Например: int global_counter; void main (void) { mutex_t mutex; ( / * И все это лишь для того, чтобы увеличить глобальную переменную на единицу. * / mutex_init (& mutex, USYNC, NULL); mutex_lock (& mutex); global_counter++; mutex_unlock (& mutex); } Если мутекс захвачен, то поток, пыта- ющийся войти в критическую секцию, блокируется. После того как мутекс ос- вобождается, один из стоящих в очере- ди потоков (если таковые накопились) разблокируется и получает возмож- ность доступа к глобальному ресурсу. Думаю, на этом рассмотрение средств синхронизации доступа к об- щим ресурсам можно закончить, хотя, разумеется, множество тем осталось за скобками. Например, в WIN32 исполь- зуется, в числе прочего, специальная разновидность мутексов под названи- ем Critical Section Object. Необходимо также помнить, что, кроме ОС, имею- щих WIN32 или POSIX API, существует большое число ни с чем не совмести- мых ОС, поэтому наличие средств синхронизации и особенности их ре- ализации должны рассматриваться от- дельно для каждой конкретной ОС РВ. А вот возможные неприятности в борьбе за ресурсы. Смертельный захват (Deadlock). В народе побочные проявления этой ситуации называются более прозаич- но — «зацикливание» или «зависание». А причина этого может быть доста- точно проста — «задачи не поделили ресурсы». Пусть, например, Задача А захватила ресурс клавиатуры и ждет, когда освободится ресурс дисплея, а в это время Задача В также хочет пооб- щаться с пользователем и, успев за- хватить ресурс дисплея, ждет теперь, когда освободится клавиатура. Так и будут задачи ждать друг друга до вто- рого потопа, а пользователь будет в это время смотреть на пустой экран и ругать последними словами яйцего- ловых программистов, которые не ОБЗОР Программное обеспечение 27 2/97

RkJQdWJsaXNoZXIy MTQ4NjUy